Merge "Merge IInputMethodManagerInvoker and IInputMethodManagerGlobalInvoker"
diff --git a/apct-tests/perftests/core/Android.bp b/apct-tests/perftests/core/Android.bp
index ab20fdb..98e4f45 100644
--- a/apct-tests/perftests/core/Android.bp
+++ b/apct-tests/perftests/core/Android.bp
@@ -44,6 +44,8 @@
"apct-perftests-utils",
"collector-device-lib",
"compatibility-device-util-axt",
+ "junit",
+ "junit-params",
"core-tests-support",
"guava",
],
diff --git a/apct-tests/perftests/core/src/android/libcore/DeepArrayOpsPerfTest.java b/apct-tests/perftests/core/src/android/libcore/DeepArrayOpsPerfTest.java
index 3f4f6af..3ebaa4c 100644
--- a/apct-tests/perftests/core/src/android/libcore/DeepArrayOpsPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/DeepArrayOpsPerfTest.java
@@ -20,18 +20,19 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class DeepArrayOpsPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -39,19 +40,14 @@
private Object[] mArray;
private Object[] mArray2;
- @Parameterized.Parameter(0)
- public int mArrayLength;
-
- @Parameterized.Parameters(name = "mArrayLength({0})")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(new Object[][] {{1}, {4}, {16}, {32}, {2048}});
}
- @Before
- public void setUp() throws Exception {
- mArray = new Object[mArrayLength * 14];
- mArray2 = new Object[mArrayLength * 14];
- for (int i = 0; i < mArrayLength; i += 14) {
+ public void setUp(int arrayLength) throws Exception {
+ mArray = new Object[arrayLength * 14];
+ mArray2 = new Object[arrayLength * 14];
+ for (int i = 0; i < arrayLength; i += 14) {
mArray[i] = new IntWrapper(i);
mArray2[i] = new IntWrapper(i);
@@ -99,7 +95,9 @@
}
@Test
- public void deepHashCode() {
+ @Parameters(method = "getData")
+ public void deepHashCode(int arrayLength) throws Exception {
+ setUp(arrayLength);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
Arrays.deepHashCode(mArray);
@@ -107,7 +105,9 @@
}
@Test
- public void deepEquals() {
+ @Parameters(method = "getData")
+ public void deepEquals(int arrayLength) throws Exception {
+ setUp(arrayLength);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
Arrays.deepEquals(mArray, mArray2);
diff --git a/apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java b/apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java
index 5aacfc2..20f1309 100644
--- a/apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java
@@ -20,22 +20,22 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.util.Arrays;
import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class SystemArrayCopyPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "arrayLength={0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{2}, {4}, {8}, {16}, {32}, {64}, {128}, {256}, {512}, {1024}, {2048}, {4096},
@@ -43,12 +43,10 @@
});
}
- @Parameterized.Parameter(0)
- public int arrayLength;
-
// Provides benchmarking for different types of arrays using the arraycopy function.
@Test
- public void timeSystemCharArrayCopy() {
+ @Parameters(method = "getData")
+ public void timeSystemCharArrayCopy(int arrayLength) {
final int len = arrayLength;
char[] src = new char[len];
char[] dst = new char[len];
@@ -59,7 +57,8 @@
}
@Test
- public void timeSystemByteArrayCopy() {
+ @Parameters(method = "getData")
+ public void timeSystemByteArrayCopy(int arrayLength) {
final int len = arrayLength;
byte[] src = new byte[len];
byte[] dst = new byte[len];
@@ -70,7 +69,8 @@
}
@Test
- public void timeSystemShortArrayCopy() {
+ @Parameters(method = "getData")
+ public void timeSystemShortArrayCopy(int arrayLength) {
final int len = arrayLength;
short[] src = new short[len];
short[] dst = new short[len];
@@ -81,7 +81,8 @@
}
@Test
- public void timeSystemIntArrayCopy() {
+ @Parameters(method = "getData")
+ public void timeSystemIntArrayCopy(int arrayLength) {
final int len = arrayLength;
int[] src = new int[len];
int[] dst = new int[len];
@@ -92,7 +93,8 @@
}
@Test
- public void timeSystemLongArrayCopy() {
+ @Parameters(method = "getData")
+ public void timeSystemLongArrayCopy(int arrayLength) {
final int len = arrayLength;
long[] src = new long[len];
long[] dst = new long[len];
@@ -103,7 +105,8 @@
}
@Test
- public void timeSystemFloatArrayCopy() {
+ @Parameters(method = "getData")
+ public void timeSystemFloatArrayCopy(int arrayLength) {
final int len = arrayLength;
float[] src = new float[len];
float[] dst = new float[len];
@@ -114,7 +117,8 @@
}
@Test
- public void timeSystemDoubleArrayCopy() {
+ @Parameters(method = "getData")
+ public void timeSystemDoubleArrayCopy(int arrayLength) {
final int len = arrayLength;
double[] src = new double[len];
double[] dst = new double[len];
@@ -125,7 +129,8 @@
}
@Test
- public void timeSystemBooleanArrayCopy() {
+ @Parameters(method = "getData")
+ public void timeSystemBooleanArrayCopy(int arrayLength) {
final int len = arrayLength;
boolean[] src = new boolean[len];
boolean[] dst = new boolean[len];
diff --git a/apct-tests/perftests/core/src/android/libcore/XmlSerializePerfTest.java b/apct-tests/perftests/core/src/android/libcore/XmlSerializePerfTest.java
index eec0734..b1b594d 100644
--- a/apct-tests/perftests/core/src/android/libcore/XmlSerializePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/XmlSerializePerfTest.java
@@ -20,42 +20,32 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import org.xmlpull.v1.XmlSerializer;
import java.io.CharArrayWriter;
import java.lang.reflect.Constructor;
-import java.util.Arrays;
-import java.util.Collection;
import java.util.Random;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class XmlSerializePerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mDatasetAsString({0}), mSeed({1})")
- public static Collection<Object[]> data() {
- return Arrays.asList(
- new Object[][] {
- {"0.99 0.7 0.7 0.7 0.7 0.7", 854328},
- {"0.999 0.3 0.3 0.95 0.9 0.9", 854328},
- {"0.99 0.7 0.7 0.7 0.7 0.7", 312547},
- {"0.999 0.3 0.3 0.95 0.9 0.9", 312547}
- });
+ private Object[] getParams() {
+ return new Object[][] {
+ new Object[] {"0.99 0.7 0.7 0.7 0.7 0.7", 854328},
+ new Object[] {"0.999 0.3 0.3 0.95 0.9 0.9", 854328},
+ new Object[] {"0.99 0.7 0.7 0.7 0.7 0.7", 312547},
+ new Object[] {"0.999 0.3 0.3 0.95 0.9 0.9", 312547}
+ };
}
- @Parameterized.Parameter(0)
- public String mDatasetAsString;
-
- @Parameterized.Parameter(1)
- public int mSeed;
-
double[] mDataset;
private Constructor<? extends XmlSerializer> mKxmlConstructor;
private Constructor<? extends XmlSerializer> mFastConstructor;
@@ -100,8 +90,7 @@
}
@SuppressWarnings("unchecked")
- @Before
- public void setUp() throws Exception {
+ public void setUp(String datasetAsString) throws Exception {
mKxmlConstructor =
(Constructor)
Class.forName("com.android.org.kxml2.io.KXmlSerializer").getConstructor();
@@ -109,28 +98,32 @@
(Constructor)
Class.forName("com.android.internal.util.FastXmlSerializer")
.getConstructor();
- String[] splitStrings = mDatasetAsString.split(" ");
+ String[] splitStrings = datasetAsString.split(" ");
mDataset = new double[splitStrings.length];
for (int i = 0; i < splitStrings.length; i++) {
mDataset[i] = Double.parseDouble(splitStrings[i]);
}
}
- private void internalTimeSerializer(Constructor<? extends XmlSerializer> ctor)
+ private void internalTimeSerializer(Constructor<? extends XmlSerializer> ctor, int seed)
throws Exception {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- serializeRandomXml(ctor, mSeed);
+ serializeRandomXml(ctor, seed);
}
}
@Test
- public void timeKxml() throws Exception {
- internalTimeSerializer(mKxmlConstructor);
+ @Parameters(method = "getParams")
+ public void timeKxml(String datasetAsString, int seed) throws Exception {
+ setUp(datasetAsString);
+ internalTimeSerializer(mKxmlConstructor, seed);
}
@Test
- public void timeFast() throws Exception {
- internalTimeSerializer(mFastConstructor);
+ @Parameters(method = "getParams")
+ public void timeFast(String datasetAsString, int seed) throws Exception {
+ setUp(datasetAsString);
+ internalTimeSerializer(mFastConstructor, seed);
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java b/apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java
index 31c92ba..3a45d40 100644
--- a/apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java
@@ -20,12 +20,12 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.io.File;
import java.io.FileOutputStream;
@@ -38,23 +38,18 @@
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class ZipFilePerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
private File mFile;
- @Parameters(name = "numEntries={0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(new Object[][] {{128}, {1024}, {8192}});
}
- @Parameterized.Parameter(0)
- public int numEntries;
-
- @Before
- public void setUp() throws Exception {
+ public void setUp(int numEntries) throws Exception {
mFile = File.createTempFile(getClass().getName(), ".zip");
mFile.deleteOnExit();
writeEntries(new ZipOutputStream(new FileOutputStream(mFile)), numEntries, 0);
@@ -66,7 +61,9 @@
}
@Test
- public void timeZipFileOpen() throws Exception {
+ @Parameters(method = "getData")
+ public void timeZipFileOpen(int numEntries) throws Exception {
+ setUp(numEntries);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
ZipFile zf = new ZipFile(mFile);
diff --git a/apct-tests/perftests/core/src/android/libcore/ZipFileReadPerfTest.java b/apct-tests/perftests/core/src/android/libcore/ZipFileReadPerfTest.java
index faa9628..2e89518 100644
--- a/apct-tests/perftests/core/src/android/libcore/ZipFileReadPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/ZipFileReadPerfTest.java
@@ -20,12 +20,13 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.io.File;
import java.io.FileOutputStream;
@@ -39,21 +40,17 @@
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class ZipFileReadPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "readBufferSize={0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(new Object[][] {{1024}, {16384}, {65536}});
}
private File mFile;
- @Parameterized.Parameter(0)
- public int readBufferSize;
-
@Before
public void setUp() throws Exception {
mFile = File.createTempFile(getClass().getName(), ".zip");
@@ -90,7 +87,8 @@
}
@Test
- public void timeZipFileRead() throws Exception {
+ @Parameters(method = "getData")
+ public void timeZipFileRead(int readBufferSize) throws Exception {
byte[] readBuffer = new byte[readBufferSize];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/BitSetPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/BitSetPerfTest.java
index db5462c..2c0473e 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/BitSetPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/BitSetPerfTest.java
@@ -20,96 +20,99 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class BitSetPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mSize={0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(new Object[][] {{1000}, {10000}});
}
- @Parameterized.Parameter(0)
- public int mSize;
-
- private BitSet mBitSet;
-
- @Before
- public void setUp() throws Exception {
- mBitSet = new BitSet(mSize);
- }
-
@Test
- public void timeIsEmptyTrue() {
+ @Parameters(method = "getData")
+ public void timeIsEmptyTrue(int size) {
+ BitSet bitSet = new BitSet(size);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- if (!mBitSet.isEmpty()) throw new RuntimeException();
+ if (!bitSet.isEmpty()) throw new RuntimeException();
}
}
@Test
- public void timeIsEmptyFalse() {
- mBitSet.set(mBitSet.size() - 1);
+ @Parameters(method = "getData")
+ public void timeIsEmptyFalse(int size) {
+ BitSet bitSet = new BitSet(size);
+ bitSet.set(bitSet.size() - 1);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- if (mBitSet.isEmpty()) throw new RuntimeException();
+ if (bitSet.isEmpty()) throw new RuntimeException();
}
}
@Test
- public void timeGet() {
+ @Parameters(method = "getData")
+ public void timeGet(int size) {
+ BitSet bitSet = new BitSet(size);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
int i = 1;
while (state.keepRunning()) {
- mBitSet.get(++i % mSize);
+ bitSet.get(++i % size);
}
}
@Test
- public void timeClear() {
+ @Parameters(method = "getData")
+ public void timeClear(int size) {
+ BitSet bitSet = new BitSet(size);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
int i = 1;
while (state.keepRunning()) {
- mBitSet.clear(++i % mSize);
+ bitSet.clear(++i % size);
}
}
@Test
- public void timeSet() {
+ @Parameters(method = "getData")
+ public void timeSet(int size) {
+ BitSet bitSet = new BitSet(size);
int i = 1;
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mBitSet.set(++i % mSize);
+ bitSet.set(++i % size);
}
}
@Test
- public void timeSetOn() {
+ @Parameters(method = "getData")
+ public void timeSetOn(int size) {
+ BitSet bitSet = new BitSet(size);
int i = 1;
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mBitSet.set(++i % mSize, true);
+ bitSet.set(++i % size, true);
}
}
@Test
- public void timeSetOff() {
+ @Parameters(method = "getData")
+ public void timeSetOff(int size) {
+ BitSet bitSet = new BitSet(size);
int i = 1;
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mBitSet.set(++i % mSize, false);
+ bitSet.set(++i % size, false);
}
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/BreakIteratorPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/BreakIteratorPerfTest.java
index 3952c12..6a2ce58 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/BreakIteratorPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/BreakIteratorPerfTest.java
@@ -20,18 +20,19 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.text.BreakIterator;
import java.util.Arrays;
import java.util.Collection;
import java.util.Locale;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public final class BreakIteratorPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -41,36 +42,37 @@
Locale.US,
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi mollis consequat"
+ " nisl non pharetra. Praesent pretium vehicula odio sed ultrices. Aenean a"
- + " felis libero. Vivamus sed commodo nibh. Pellentesque turpis lectus, euismod"
- + " vel ante nec, cursus posuere orci. Suspendisse velit neque, fermentum"
- + " luctus ultrices in, ultrices vitae arcu. Duis tincidunt cursus lorem. Nam"
- + " ultricies accumsan quam vitae imperdiet. Pellentesque habitant morbi"
- + " tristique senectus et netus et malesuada fames ac turpis egestas. Quisque"
- + " aliquet pretium nisi, eget laoreet enim molestie sit amet. Class aptent"
- + " taciti sociosqu ad litora torquent per conubia nostra, per inceptos"
+ + " felis libero. Vivamus sed commodo nibh. Pellentesque turpis lectus,"
+ + " euismod vel ante nec, cursus posuere orci. Suspendisse velit neque,"
+ + " fermentum luctus ultrices in, ultrices vitae arcu. Duis tincidunt cursus"
+ + " lorem. Nam ultricies accumsan quam vitae imperdiet. Pellentesque habitant"
+ + " morbi tristique senectus et netus et malesuada fames ac turpis egestas."
+ + " Quisque aliquet pretium nisi, eget laoreet enim molestie sit amet. Class"
+ + " aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos"
+ " himenaeos.\n"
+ "Nam dapibus aliquam lacus ac suscipit. Proin in nibh sit amet purus congue"
+ " laoreet eget quis nisl. Morbi gravida dignissim justo, a venenatis ante"
- + " pulvinar at. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin"
- + " ultrices vestibulum dui, vel aliquam lacus aliquam quis. Duis fringilla"
- + " sapien ac lacus egestas, vel adipiscing elit euismod. Donec non tellus"
- + " odio. Donec gravida eu massa ac feugiat. Aliquam erat volutpat. Praesent id"
- + " adipiscing metus, nec laoreet enim. Aliquam vitae posuere turpis. Mauris ac"
- + " pharetra sem. In at placerat tortor. Vivamus ac vehicula neque. Cras"
- + " volutpat ullamcorper massa et varius. Praesent sagittis neque vitae nulla"
- + " euismod pharetra.\n"
+ + " pulvinar at. Lorem ipsum dolor sit amet, consectetur adipiscing elit."
+ + " Proin ultrices vestibulum dui, vel aliquam lacus aliquam quis. Duis"
+ + " fringilla sapien ac lacus egestas, vel adipiscing elit euismod. Donec non"
+ + " tellus odio. Donec gravida eu massa ac feugiat. Aliquam erat volutpat."
+ + " Praesent id adipiscing metus, nec laoreet enim. Aliquam vitae posuere"
+ + " turpis. Mauris ac pharetra sem. In at placerat tortor. Vivamus ac vehicula"
+ + " neque. Cras volutpat ullamcorper massa et varius. Praesent sagittis neque"
+ + " vitae nulla euismod pharetra.\n"
+ "Sed placerat sapien non molestie sollicitudin. Nullam sit amet dictum quam."
+ " Etiam tincidunt tortor vel pretium vehicula. Praesent fringilla ipsum vel"
+ " velit luctus dignissim. Nulla massa ligula, mattis in enim et, mattis"
+ " lacinia odio. Suspendisse tristique urna a orci commodo tempor. Duis"
+ " lacinia egestas arcu a sollicitudin.\n"
+ "In ac feugiat lacus. Nunc fermentum eu est at tristique. Pellentesque quis"
- + " ligula et orci placerat lacinia. Maecenas quis mauris diam. Etiam mi ipsum,"
- + " tempus in purus quis, euismod faucibus orci. Nulla facilisi. Praesent sit"
- + " amet sapien vel elit porta adipiscing. Phasellus sit amet volutpat diam.\n"
- + "Proin bibendum elit non lacus pharetra, quis eleifend tellus placerat. Nulla"
- + " facilisi. Maecenas ante diam, pellentesque mattis mattis in, porta ut"
- + " lorem. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices"
+ + " ligula et orci placerat lacinia. Maecenas quis mauris diam. Etiam mi"
+ + " ipsum, tempus in purus quis, euismod faucibus orci. Nulla facilisi."
+ + " Praesent sit amet sapien vel elit porta adipiscing. Phasellus sit amet"
+ + " volutpat diam.\n"
+ + "Proin bibendum elit non lacus pharetra, quis eleifend tellus placerat."
+ + " Nulla facilisi. Maecenas ante diam, pellentesque mattis mattis in, porta"
+ + " ut lorem. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices"
+ " posuere cubilia Curae; Nunc interdum tristique metus, in scelerisque odio"
+ " fermentum eget. Cras nec venenatis lacus. Aenean euismod eget metus quis"
+ " molestie. Cras tincidunt dolor ut massa ornare, in elementum lacus auctor."
@@ -80,29 +82,29 @@
LONGPARA(
Locale.US,
"During dinner, Mr. Bennet scarcely spoke at all; but when the servants were"
- + " withdrawn, he thought it time to have some conversation with his guest, and"
- + " therefore started a subject in which he expected him to shine, by observing"
- + " that he seemed very fortunate in his patroness. Lady Catherine de Bourgh's"
- + " attention to his wishes, and consideration for his comfort, appeared very"
- + " remarkable. Mr. Bennet could not have chosen better. Mr. Collins was"
- + " eloquent in her praise. The subject elevated him to more than usual"
- + " solemnity of manner, and with a most important aspect he protested that"
- + " \"he had never in his life witnessed such behaviour in a person of"
- + " rank--such affability and condescension, as he had himself experienced from"
- + " Lady Catherine. She had been graciously pleased to approve of both of the"
- + " discourses which he had already had the honour of preaching before her. She"
- + " had also asked him twice to dine at Rosings, and had sent for him only the"
- + " Saturday before, to make up her pool of quadrille in the evening. Lady"
- + " Catherine was reckoned proud by many people he knew, but _he_ had never"
- + " seen anything but affability in her. She had always spoken to him as she"
- + " would to any other gentleman; she made not the smallest objection to his"
- + " joining in the society of the neighbourhood nor to his leaving the parish"
- + " occasionally for a week or two, to visit his relations. She had even"
- + " condescended to advise him to marry as soon as he could, provided he chose"
- + " with discretion; and had once paid him a visit in his humble parsonage,"
- + " where she had perfectly approved all the alterations he had been making,"
- + " and had even vouchsafed to suggest some herself--some shelves in the closet"
- + " up stairs.\""),
+ + " withdrawn, he thought it time to have some conversation with his guest,"
+ + " and therefore started a subject in which he expected him to shine, by"
+ + " observing that he seemed very fortunate in his patroness. Lady Catherine"
+ + " de Bourgh's attention to his wishes, and consideration for his comfort,"
+ + " appeared very remarkable. Mr. Bennet could not have chosen better. Mr."
+ + " Collins was eloquent in her praise. The subject elevated him to more than"
+ + " usual solemnity of manner, and with a most important aspect he protested"
+ + " that \"he had never in his life witnessed such behaviour in a person of"
+ + " rank--such affability and condescension, as he had himself experienced"
+ + " from Lady Catherine. She had been graciously pleased to approve of both of"
+ + " the discourses which he had already had the honour of preaching before"
+ + " her. She had also asked him twice to dine at Rosings, and had sent for him"
+ + " only the Saturday before, to make up her pool of quadrille in the evening."
+ + " Lady Catherine was reckoned proud by many people he knew, but _he_ had"
+ + " never seen anything but affability in her. She had always spoken to him as"
+ + " she would to any other gentleman; she made not the smallest objection to"
+ + " his joining in the society of the neighbourhood nor to his leaving the"
+ + " parish occasionally for a week or two, to visit his relations. She had"
+ + " even condescended to advise him to marry as soon as he could, provided he"
+ + " chose with discretion; and had once paid him a visit in his humble"
+ + " parsonage, where she had perfectly approved all the alterations he had"
+ + " been making, and had even vouchsafed to suggest some herself--some shelves"
+ + " in the closet up stairs.\""),
GERMAN(
Locale.GERMANY,
"Aber dieser Freiheit setzte endlich der Winter ein Ziel. Draußen auf den Feldern"
@@ -119,15 +121,14 @@
+ " เดิมทีเป็นการผสมผสานกันระหว่างสำเนียงอยุธยาและชาวไทยเชื้อสายจีนรุ่นหลังที่"
+ "พูดไทยแทนกลุ่มภาษาจีน"
+ " ลักษณะเด่นคือมีการออกเสียงที่ชัดเจนและแข็งกระด้างซึ่งได้รับอิทธิพลจากภาษาแต"
- + "้จิ๋ว"
- + " การออกเสียงพยัญชนะ สระ การผันวรรณยุกต์ที่ในภาษาไทยมาตรฐาน"
+ + "้จิ๋ว การออกเสียงพยัญชนะ สระ การผันวรรณยุกต์ที่ในภาษาไทยมาตรฐาน"
+ " มาจากสำเนียงถิ่นนี้ในขณะที่ภาษาไทยสำเนียงอื่นล้วนเหน่อทั้งสิ้น"
+ " คำศัพท์ที่ใช้ในสำเนียงกรุงเทพจำนวนมากได้รับมาจากกลุ่มภาษาจีนเช่นคำว่า โป๊,"
+ " เฮ็ง, อาหมวย, อาซิ่ม ซึ่งมาจากภาษาแต้จิ๋ว และจากภาษาจีนเช่น ถู(涂), ชิ่ว(去"
+ " อ่านว่า\"ชู่\") และคำว่า ทาย(猜 อ่านว่า \"ชาย\") เป็นต้น"
+ " เนื่องจากสำเนียงกรุงเทพได้รับอิทธิพลมาจากภาษาจีนดังนั้นตัวอักษร \"ร\""
- + " มักออกเสียงเหมารวมเป็น \"ล\" หรือคำควบกล่ำบางคำถูกละทิ้งไปด้วยเช่น รู้ เป็น"
- + " ลู้, เรื่อง เป็น เลื่อง หรือ ประเทศ เป็น ปะเทศ"
+ + " มักออกเสียงเหมารวมเป็น \"ล\" หรือคำควบกล่ำบางคำถูกละทิ้งไปด้วยเช่น รู้"
+ + " เป็น ลู้, เรื่อง เป็น เลื่อง หรือ ประเทศ เป็น ปะเทศ"
+ " เป็นต้นสร้างความลำบากให้แก่ต่างชาติที่ต้องการเรียนภาษาไทย"
+ " แต่อย่างไรก็ตามผู้ที่พูดสำเนียงถิ่นนี้ก็สามารถออกอักขระภาษาไทยตามมาตรฐานได"
+ "้อย่างถูกต้องเพียงแต่มักเผลอไม่ค่อยออกเสียง"),
@@ -151,8 +152,7 @@
}
}
- @Parameters(name = "mText={0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{Text.ACCENT}, {Text.BIDI}, {Text.EMOJI}, {Text.EMPTY}, {Text.GERMAN},
@@ -161,15 +161,13 @@
});
}
- @Parameterized.Parameter(0)
- public Text mText;
-
@Test
- public void timeBreakIterator() {
+ @Parameters(method = "getData")
+ public void timeBreakIterator(Text text) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- BreakIterator it = BreakIterator.getLineInstance(mText.mLocale);
- it.setText(mText.mText);
+ BreakIterator it = BreakIterator.getLineInstance(text.mLocale);
+ it.setText(text.mText);
while (it.next() != BreakIterator.DONE) {
// Keep iterating
@@ -178,12 +176,13 @@
}
@Test
- public void timeIcuBreakIterator() {
+ @Parameters(method = "getData")
+ public void timeIcuBreakIterator(Text text) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
android.icu.text.BreakIterator it =
- android.icu.text.BreakIterator.getLineInstance(mText.mLocale);
- it.setText(mText.mText);
+ android.icu.text.BreakIterator.getLineInstance(text.mLocale);
+ it.setText(text.mText);
while (it.next() != android.icu.text.BreakIterator.DONE) {
// Keep iterating
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/BulkPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/BulkPerfTest.java
index 855bb9a..b7b7e83 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/BulkPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/BulkPerfTest.java
@@ -20,11 +20,12 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.io.File;
import java.io.IOException;
@@ -34,13 +35,12 @@
import java.util.Arrays;
import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class BulkPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mAlign({0}), mSBuf({1}), mDBuf({2}), mSize({3})")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{true, MyBufferType.DIRECT, MyBufferType.DIRECT, 4096},
@@ -82,24 +82,12 @@
});
}
- @Parameterized.Parameter(0)
- public boolean mAlign;
-
enum MyBufferType {
DIRECT,
HEAP,
MAPPED
}
- @Parameterized.Parameter(1)
- public MyBufferType mSBuf;
-
- @Parameterized.Parameter(2)
- public MyBufferType mDBuf;
-
- @Parameterized.Parameter(3)
- public int mSize;
-
public static ByteBuffer newBuffer(boolean aligned, MyBufferType bufferType, int bsize)
throws IOException {
int size = aligned ? bsize : bsize + 8 + 1;
@@ -126,13 +114,15 @@
}
@Test
- public void timePut() throws Exception {
- ByteBuffer src = BulkPerfTest.newBuffer(mAlign, mSBuf, mSize);
- ByteBuffer data = BulkPerfTest.newBuffer(mAlign, mDBuf, mSize);
+ @Parameters(method = "getData")
+ public void timePut(boolean align, MyBufferType sBuf, MyBufferType dBuf, int size)
+ throws Exception {
+ ByteBuffer src = BulkPerfTest.newBuffer(align, sBuf, size);
+ ByteBuffer data = BulkPerfTest.newBuffer(align, dBuf, size);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAlign ? 0 : 1);
- data.position(mAlign ? 0 : 1);
+ src.position(align ? 0 : 1);
+ data.position(align ? 0 : 1);
src.put(data);
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferPerfTest.java
index 4bd7c4e..9ac36d0 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferPerfTest.java
@@ -20,11 +20,12 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.io.File;
import java.io.IOException;
@@ -41,7 +42,7 @@
import java.util.Arrays;
import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class ByteBufferPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -49,15 +50,14 @@
public enum MyByteOrder {
BIG(ByteOrder.BIG_ENDIAN),
LITTLE(ByteOrder.LITTLE_ENDIAN);
- final ByteOrder mByteOrder;
+ final ByteOrder byteOrder;
- MyByteOrder(ByteOrder mByteOrder) {
- this.mByteOrder = mByteOrder;
+ MyByteOrder(ByteOrder byteOrder) {
+ this.byteOrder = byteOrder;
}
}
- @Parameters(name = "mByteOrder={0}, mAligned={1}, mBufferType={2}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{MyByteOrder.BIG, true, MyBufferType.DIRECT},
@@ -75,21 +75,12 @@
});
}
- @Parameterized.Parameter(0)
- public MyByteOrder mByteOrder;
-
- @Parameterized.Parameter(1)
- public boolean mAligned;
-
enum MyBufferType {
DIRECT,
HEAP,
MAPPED;
}
- @Parameterized.Parameter(2)
- public MyBufferType mBufferType;
-
public static ByteBuffer newBuffer(
MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws IOException {
int size = aligned ? 8192 : 8192 + 8 + 1;
@@ -115,7 +106,7 @@
result = fc.map(FileChannel.MapMode.READ_WRITE, 0, fc.size());
break;
}
- result.order(byteOrder.mByteOrder);
+ result.order(byteOrder.byteOrder);
result.position(aligned ? 0 : 1);
return result;
}
@@ -125,11 +116,13 @@
//
@Test
- public void timeByteBuffer_getByte() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_getByte(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.get();
}
@@ -137,24 +130,28 @@
}
@Test
- public void timeByteBuffer_getByteArray() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_getByteArray(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
byte[] dst = new byte[1024];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
for (int i = 0; i < 1024; ++i) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
src.get(dst);
}
}
}
@Test
- public void timeByteBuffer_getByte_indexed() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_getByte_indexed(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.get(i);
}
@@ -162,11 +159,13 @@
}
@Test
- public void timeByteBuffer_getChar() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_getChar(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.getChar();
}
@@ -174,9 +173,11 @@
}
@Test
- public void timeCharBuffer_getCharArray() throws Exception {
+ @Parameters(method = "getData")
+ public void timeCharBuffer_getCharArray(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
CharBuffer src =
- ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asCharBuffer();
+ ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asCharBuffer();
char[] dst = new char[1024];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -188,11 +189,13 @@
}
@Test
- public void timeByteBuffer_getChar_indexed() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_getChar_indexed(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.getChar(i * 2);
}
@@ -200,11 +203,13 @@
}
@Test
- public void timeByteBuffer_getDouble() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_getDouble(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.getDouble();
}
@@ -212,9 +217,11 @@
}
@Test
- public void timeDoubleBuffer_getDoubleArray() throws Exception {
+ @Parameters(method = "getData")
+ public void timeDoubleBuffer_getDoubleArray(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
DoubleBuffer src =
- ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asDoubleBuffer();
+ ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asDoubleBuffer();
double[] dst = new double[1024];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -226,11 +233,13 @@
}
@Test
- public void timeByteBuffer_getFloat() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_getFloat(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.getFloat();
}
@@ -238,9 +247,11 @@
}
@Test
- public void timeFloatBuffer_getFloatArray() throws Exception {
+ @Parameters(method = "getData")
+ public void timeFloatBuffer_getFloatArray(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
FloatBuffer src =
- ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asFloatBuffer();
+ ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asFloatBuffer();
float[] dst = new float[1024];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -252,11 +263,13 @@
}
@Test
- public void timeByteBuffer_getInt() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_getInt(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.getInt();
}
@@ -264,9 +277,10 @@
}
@Test
- public void timeIntBuffer_getIntArray() throws Exception {
- IntBuffer src =
- ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asIntBuffer();
+ @Parameters(method = "getData")
+ public void timeIntBuffer_getIntArray(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ IntBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asIntBuffer();
int[] dst = new int[1024];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -278,11 +292,13 @@
}
@Test
- public void timeByteBuffer_getLong() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_getLong(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.getLong();
}
@@ -290,9 +306,11 @@
}
@Test
- public void timeLongBuffer_getLongArray() throws Exception {
+ @Parameters(method = "getData")
+ public void timeLongBuffer_getLongArray(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
LongBuffer src =
- ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asLongBuffer();
+ ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asLongBuffer();
long[] dst = new long[1024];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -304,11 +322,13 @@
}
@Test
- public void timeByteBuffer_getShort() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_getShort(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.getShort();
}
@@ -316,9 +336,11 @@
}
@Test
- public void timeShortBuffer_getShortArray() throws Exception {
+ @Parameters(method = "getData")
+ public void timeShortBuffer_getShortArray(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
ShortBuffer src =
- ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asShortBuffer();
+ ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asShortBuffer();
short[] dst = new short[1024];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -334,8 +356,10 @@
//
@Test
- public void timeByteBuffer_putByte() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_putByte(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
src.position(0);
@@ -346,24 +370,28 @@
}
@Test
- public void timeByteBuffer_putByteArray() throws Exception {
- ByteBuffer dst = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_putByteArray(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer dst = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
byte[] src = new byte[1024];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
for (int i = 0; i < 1024; ++i) {
- dst.position(mAligned ? 0 : 1);
+ dst.position(aligned ? 0 : 1);
dst.put(src);
}
}
}
@Test
- public void timeByteBuffer_putChar() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_putChar(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.putChar(' ');
}
@@ -371,9 +399,11 @@
}
@Test
- public void timeCharBuffer_putCharArray() throws Exception {
+ @Parameters(method = "getData")
+ public void timeCharBuffer_putCharArray(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
CharBuffer dst =
- ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asCharBuffer();
+ ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asCharBuffer();
char[] src = new char[1024];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -385,11 +415,13 @@
}
@Test
- public void timeByteBuffer_putDouble() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_putDouble(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.putDouble(0.0);
}
@@ -397,9 +429,11 @@
}
@Test
- public void timeDoubleBuffer_putDoubleArray() throws Exception {
+ @Parameters(method = "getData")
+ public void timeDoubleBuffer_putDoubleArray(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
DoubleBuffer dst =
- ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asDoubleBuffer();
+ ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asDoubleBuffer();
double[] src = new double[1024];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -411,11 +445,13 @@
}
@Test
- public void timeByteBuffer_putFloat() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_putFloat(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.putFloat(0.0f);
}
@@ -423,9 +459,11 @@
}
@Test
- public void timeFloatBuffer_putFloatArray() throws Exception {
+ @Parameters(method = "getData")
+ public void timeFloatBuffer_putFloatArray(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
FloatBuffer dst =
- ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asFloatBuffer();
+ ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asFloatBuffer();
float[] src = new float[1024];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -437,11 +475,13 @@
}
@Test
- public void timeByteBuffer_putInt() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_putInt(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.putInt(0);
}
@@ -449,9 +489,10 @@
}
@Test
- public void timeIntBuffer_putIntArray() throws Exception {
- IntBuffer dst =
- ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asIntBuffer();
+ @Parameters(method = "getData")
+ public void timeIntBuffer_putIntArray(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ IntBuffer dst = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asIntBuffer();
int[] src = new int[1024];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -463,11 +504,13 @@
}
@Test
- public void timeByteBuffer_putLong() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_putLong(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.putLong(0L);
}
@@ -475,9 +518,11 @@
}
@Test
- public void timeLongBuffer_putLongArray() throws Exception {
+ @Parameters(method = "getData")
+ public void timeLongBuffer_putLongArray(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
LongBuffer dst =
- ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asLongBuffer();
+ ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asLongBuffer();
long[] src = new long[1024];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -489,11 +534,13 @@
}
@Test
- public void timeByteBuffer_putShort() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_putShort(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.putShort((short) 0);
}
@@ -501,9 +548,11 @@
}
@Test
- public void timeShortBuffer_putShortArray() throws Exception {
+ @Parameters(method = "getData")
+ public void timeShortBuffer_putShortArray(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
ShortBuffer dst =
- ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asShortBuffer();
+ ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asShortBuffer();
short[] src = new short[1024];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -515,6 +564,7 @@
}
@Test
+ @Parameters(method = "getData")
public void time_new_byteArray() throws Exception {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -523,6 +573,7 @@
}
@Test
+ @Parameters(method = "getData")
public void time_ByteBuffer_allocate() throws Exception {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferScalarVersusVectorPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferScalarVersusVectorPerfTest.java
index 81f9e59..5dd9d6e 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferScalarVersusVectorPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferScalarVersusVectorPerfTest.java
@@ -20,23 +20,23 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class ByteBufferScalarVersusVectorPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mByteOrder={0}, mAligned={1}, mBufferType={2}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{
@@ -102,19 +102,15 @@
});
}
- @Parameterized.Parameter(0)
- public ByteBufferPerfTest.MyByteOrder mByteOrder;
-
- @Parameterized.Parameter(1)
- public boolean mAligned;
-
- @Parameterized.Parameter(2)
- public ByteBufferPerfTest.MyBufferType mBufferType;
-
@Test
- public void timeManualByteBufferCopy() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
- ByteBuffer dst = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeManualByteBufferCopy(
+ ByteBufferPerfTest.MyByteOrder byteOrder,
+ boolean aligned,
+ ByteBufferPerfTest.MyBufferType bufferType)
+ throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
+ ByteBuffer dst = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
src.position(0);
@@ -126,23 +122,25 @@
}
@Test
- public void timeByteBufferBulkGet() throws Exception {
- ByteBuffer src = ByteBuffer.allocate(mAligned ? 8192 : 8192 + 1);
+ @Parameters({"true", "false"})
+ public void timeByteBufferBulkGet(boolean aligned) throws Exception {
+ ByteBuffer src = ByteBuffer.allocate(aligned ? 8192 : 8192 + 1);
byte[] dst = new byte[8192];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
src.get(dst, 0, dst.length);
}
}
@Test
- public void timeDirectByteBufferBulkGet() throws Exception {
- ByteBuffer src = ByteBuffer.allocateDirect(mAligned ? 8192 : 8192 + 1);
+ @Parameters({"true", "false"})
+ public void timeDirectByteBufferBulkGet(boolean aligned) throws Exception {
+ ByteBuffer src = ByteBuffer.allocateDirect(aligned ? 8192 : 8192 + 1);
byte[] dst = new byte[8192];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
src.get(dst, 0, dst.length);
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/CharacterPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/CharacterPerfTest.java
index 28ec6de..0a59899 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/CharacterPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/CharacterPerfTest.java
@@ -20,12 +20,12 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.util.Arrays;
import java.util.Collection;
@@ -34,13 +34,12 @@
* Tests various Character methods, intended for testing multiple implementations against each
* other.
*/
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class CharacterPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mCharacterSet({0}), mOverload({1})")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{CharacterSet.ASCII, Overload.CHAR},
@@ -50,17 +49,10 @@
});
}
- @Parameterized.Parameter(0)
- public CharacterSet mCharacterSet;
-
- @Parameterized.Parameter(1)
- public Overload mOverload;
-
private char[] mChars;
- @Before
- public void setUp() throws Exception {
- this.mChars = mCharacterSet.mChars;
+ public void setUp(CharacterSet characterSet) {
+ this.mChars = characterSet.mChars;
}
public enum Overload {
@@ -87,10 +79,12 @@
// A fake benchmark to give us a baseline.
@Test
- public void timeIsSpace() {
+ @Parameters(method = "getData")
+ public void timeIsSpace(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
boolean fake = false;
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
fake ^= ((char) ch == ' ');
@@ -106,9 +100,11 @@
}
@Test
- public void timeDigit() {
+ @Parameters(method = "getData")
+ public void timeDigit(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
Character.digit(mChars[ch], 10);
@@ -124,9 +120,11 @@
}
@Test
- public void timeGetNumericValue() {
+ @Parameters(method = "getData")
+ public void timeGetNumericValue(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
Character.getNumericValue(mChars[ch]);
@@ -142,9 +140,11 @@
}
@Test
- public void timeIsDigit() {
+ @Parameters(method = "getData")
+ public void timeIsDigit(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
Character.isDigit(mChars[ch]);
@@ -160,9 +160,11 @@
}
@Test
- public void timeIsIdentifierIgnorable() {
+ @Parameters(method = "getData")
+ public void timeIsIdentifierIgnorable(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
Character.isIdentifierIgnorable(mChars[ch]);
@@ -178,9 +180,11 @@
}
@Test
- public void timeIsJavaIdentifierPart() {
+ @Parameters(method = "getData")
+ public void timeIsJavaIdentifierPart(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
Character.isJavaIdentifierPart(mChars[ch]);
@@ -196,9 +200,11 @@
}
@Test
- public void timeIsJavaIdentifierStart() {
+ @Parameters(method = "getData")
+ public void timeIsJavaIdentifierStart(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
Character.isJavaIdentifierStart(mChars[ch]);
@@ -214,9 +220,11 @@
}
@Test
- public void timeIsLetter() {
+ @Parameters(method = "getData")
+ public void timeIsLetter(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
Character.isLetter(mChars[ch]);
@@ -232,9 +240,11 @@
}
@Test
- public void timeIsLetterOrDigit() {
+ @Parameters(method = "getData")
+ public void timeIsLetterOrDigit(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
Character.isLetterOrDigit(mChars[ch]);
@@ -250,9 +260,11 @@
}
@Test
- public void timeIsLowerCase() {
+ @Parameters(method = "getData")
+ public void timeIsLowerCase(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
Character.isLowerCase(mChars[ch]);
@@ -268,9 +280,11 @@
}
@Test
- public void timeIsSpaceChar() {
+ @Parameters(method = "getData")
+ public void timeIsSpaceChar(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
Character.isSpaceChar(mChars[ch]);
@@ -286,9 +300,11 @@
}
@Test
- public void timeIsUpperCase() {
+ @Parameters(method = "getData")
+ public void timeIsUpperCase(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
Character.isUpperCase(mChars[ch]);
@@ -304,9 +320,11 @@
}
@Test
- public void timeIsWhitespace() {
+ @Parameters(method = "getData")
+ public void timeIsWhitespace(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
Character.isWhitespace(mChars[ch]);
@@ -322,9 +340,11 @@
}
@Test
- public void timeToLowerCase() {
+ @Parameters(method = "getData")
+ public void timeToLowerCase(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
Character.toLowerCase(mChars[ch]);
@@ -340,9 +360,11 @@
}
@Test
- public void timeToUpperCase() {
+ @Parameters(method = "getData")
+ public void timeToUpperCase(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
Character.toUpperCase(mChars[ch]);
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/CharsetForNamePerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/CharsetForNamePerfTest.java
index 603b182..8da13a9 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/CharsetForNamePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/CharsetForNamePerfTest.java
@@ -20,44 +20,40 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
import java.nio.charset.Charset;
-import java.util.Arrays;
-import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class CharsetForNamePerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameterized.Parameters(name = "mCharsetName({0})")
- public static Collection<Object[]> data() {
- return Arrays.asList(
- new Object[][] {
- {"UTF-16"},
- {"UTF-8"},
- {"UTF8"},
- {"ISO-8859-1"},
- {"8859_1"},
- {"ISO-8859-2"},
- {"8859_2"},
- {"US-ASCII"},
- {"ASCII"},
- });
+ public static String[] charsetNames() {
+ return new String[] {
+ "UTF-16",
+ "UTF-8",
+ "UTF8",
+ "ISO-8859-1",
+ "8859_1",
+ "ISO-8859-2",
+ "8859_2",
+ "US-ASCII",
+ "ASCII",
+ };
}
- @Parameterized.Parameter(0)
- public String mCharsetName;
-
@Test
- public void timeCharsetForName() throws Exception {
+ @Parameters(method = "charsetNames")
+ public void timeCharsetForName(String charsetName) throws Exception {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- Charset.forName(mCharsetName);
+ Charset.forName(charsetName);
}
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/CharsetPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/CharsetPerfTest.java
index 437d186..048c50f 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/CharsetPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/CharsetPerfTest.java
@@ -20,22 +20,22 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.util.Arrays;
import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class CharsetPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mLength({0}), mName({1})")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{1, "UTF-16"},
@@ -86,24 +86,20 @@
});
}
- @Parameterized.Parameter(0)
- public int mLength;
-
- @Parameterized.Parameter(1)
- public String mName;
-
@Test
- public void time_new_String_BString() throws Exception {
- byte[] bytes = makeBytes(makeString(mLength));
+ @Parameters(method = "getData")
+ public void time_new_String_BString(int length, String name) throws Exception {
+ byte[] bytes = makeBytes(makeString(length));
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- new String(bytes, mName);
+ new String(bytes, name);
}
}
@Test
- public void time_new_String_BII() throws Exception {
- byte[] bytes = makeBytes(makeString(mLength));
+ @Parameters(method = "getData")
+ public void time_new_String_BII(int length, String name) throws Exception {
+ byte[] bytes = makeBytes(makeString(length));
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
new String(bytes, 0, bytes.length);
@@ -111,20 +107,22 @@
}
@Test
- public void time_new_String_BIIString() throws Exception {
- byte[] bytes = makeBytes(makeString(mLength));
+ @Parameters(method = "getData")
+ public void time_new_String_BIIString(int length, String name) throws Exception {
+ byte[] bytes = makeBytes(makeString(length));
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- new String(bytes, 0, bytes.length, mName);
+ new String(bytes, 0, bytes.length, name);
}
}
@Test
- public void time_String_getBytes() throws Exception {
- String string = makeString(mLength);
+ @Parameters(method = "getData")
+ public void time_String_getBytes(int length, String name) throws Exception {
+ String string = makeString(length);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- string.getBytes(mName);
+ string.getBytes(name);
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/CipherPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/CipherPerfTest.java
index 15c27f2..42b0588 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/CipherPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/CipherPerfTest.java
@@ -20,11 +20,12 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
import java.security.spec.AlgorithmParameterSpec;
import java.util.ArrayList;
@@ -42,17 +43,13 @@
* Cipher benchmarks. Only runs on AES currently because of the combinatorial explosion of the test
* as it stands.
*/
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class CipherPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameterized.Parameters(
- name =
- "mMode({0}), mPadding({1}), mKeySize({2}), mInputSize({3}),"
- + " mImplementation({4})")
- public static Collection cases() {
- int[] mKeySizes = new int[] {128, 192, 256};
+ public static Collection getCases() {
+ int[] keySizes = new int[] {128, 192, 256};
int[] inputSizes = new int[] {16, 32, 64, 128, 1024, 8192};
final List<Object[]> params = new ArrayList<>();
for (Mode mode : Mode.values()) {
@@ -71,11 +68,11 @@
&& implementation == Implementation.OpenSSL) {
continue;
}
- for (int mKeySize : mKeySizes) {
+ for (int keySize : keySizes) {
for (int inputSize : inputSizes) {
params.add(
new Object[] {
- mode, padding, mKeySize, inputSize, implementation
+ mode, padding, keySize, inputSize, implementation
});
}
}
@@ -107,9 +104,6 @@
AES,
};
- @Parameterized.Parameter(0)
- public Mode mMode;
-
public enum Mode {
CBC,
CFB,
@@ -118,23 +112,11 @@
OFB,
};
- @Parameterized.Parameter(1)
- public Padding mPadding;
-
public enum Padding {
NOPADDING,
PKCS1PADDING,
};
- @Parameterized.Parameter(2)
- public int mKeySize;
-
- @Parameterized.Parameter(3)
- public int mInputSize;
-
- @Parameterized.Parameter(4)
- public Implementation mImplementation;
-
public enum Implementation {
OpenSSL,
BouncyCastle
@@ -156,21 +138,20 @@
private AlgorithmParameterSpec mSpec;
- @Before
- public void setUp() throws Exception {
- mCipherAlgorithm =
- mAlgorithm.toString() + "/" + mMode.toString() + "/" + mPadding.toString();
+ public void setUp(Mode mode, Padding padding, int keySize, Implementation implementation)
+ throws Exception {
+ mCipherAlgorithm = mAlgorithm.toString() + "/" + mode.toString() + "/" + padding.toString();
String mKeyAlgorithm = mAlgorithm.toString();
- mKey = sKeySizes.get(mKeySize);
+ mKey = sKeySizes.get(keySize);
if (mKey == null) {
KeyGenerator generator = KeyGenerator.getInstance(mKeyAlgorithm);
- generator.init(mKeySize);
+ generator.init(keySize);
mKey = generator.generateKey();
- sKeySizes.put(mKeySize, mKey);
+ sKeySizes.put(keySize, mKey);
}
- switch (mImplementation) {
+ switch (implementation) {
case OpenSSL:
mProviderName = "AndroidOpenSSL";
break;
@@ -178,10 +159,10 @@
mProviderName = "BC";
break;
default:
- throw new RuntimeException(mImplementation.toString());
+ throw new RuntimeException(implementation.toString());
}
- if (mMode != Mode.ECB) {
+ if (mode != Mode.ECB) {
mSpec = new IvParameterSpec(IV);
}
@@ -193,18 +174,26 @@
}
@Test
- public void timeEncrypt() throws Exception {
+ @Parameters(method = "getCases")
+ public void timeEncrypt(
+ Mode mode, Padding padding, int keySize, int inputSize, Implementation implementation)
+ throws Exception {
+ setUp(mode, padding, keySize, implementation);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mCipherEncrypt.doFinal(DATA, 0, mInputSize, mOutput);
+ mCipherEncrypt.doFinal(DATA, 0, inputSize, mOutput);
}
}
@Test
- public void timeDecrypt() throws Exception {
+ @Parameters(method = "getCases")
+ public void timeDecrypt(
+ Mode mode, Padding padding, int keySize, int inputSize, Implementation implementation)
+ throws Exception {
+ setUp(mode, padding, keySize, implementation);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mCipherDecrypt.doFinal(DATA, 0, mInputSize, mOutput);
+ mCipherDecrypt.doFinal(DATA, 0, inputSize, mOutput);
}
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/CollectionsPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/CollectionsPerfTest.java
index a89efff..69197c3 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/CollectionsPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/CollectionsPerfTest.java
@@ -20,11 +20,12 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.util.ArrayList;
import java.util.Arrays;
@@ -35,19 +36,15 @@
import java.util.Random;
import java.util.Vector;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class CollectionsPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mArrayListLength({0})")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(new Object[][] {{4}, {16}, {64}, {256}, {1024}});
}
- @Parameterized.Parameter(0)
- public int arrayListLength;
-
public static Comparator<Integer> REVERSE =
new Comparator<Integer>() {
@Override
@@ -59,7 +56,8 @@
};
@Test
- public void timeSort_arrayList() throws Exception {
+ @Parameters(method = "getData")
+ public void timeSort_arrayList(int arrayListLength) throws Exception {
List<Integer> input = buildList(arrayListLength, ArrayList.class);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -68,7 +66,8 @@
}
@Test
- public void timeSortWithComparator_arrayList() throws Exception {
+ @Parameters(method = "getData")
+ public void timeSortWithComparator_arrayList(int arrayListLength) throws Exception {
List<Integer> input = buildList(arrayListLength, ArrayList.class);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -77,7 +76,8 @@
}
@Test
- public void timeSort_vector() throws Exception {
+ @Parameters(method = "getData")
+ public void timeSort_vector(int arrayListLength) throws Exception {
List<Integer> input = buildList(arrayListLength, Vector.class);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -86,7 +86,8 @@
}
@Test
- public void timeSortWithComparator_vector() throws Exception {
+ @Parameters(method = "getData")
+ public void timeSortWithComparator_vector(int arrayListLength) throws Exception {
List<Integer> input = buildList(arrayListLength, Vector.class);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/EqualsHashCodePerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/EqualsHashCodePerfTest.java
index 4ff3ba5..8391203 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/EqualsHashCodePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/EqualsHashCodePerfTest.java
@@ -20,11 +20,12 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
import java.net.URI;
import java.net.URL;
@@ -32,39 +33,39 @@
import java.util.Collection;
import java.util.List;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public final class EqualsHashCodePerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
private enum Type {
URI() {
- @Override Object newInstance(String text) throws Exception {
+ @Override
+ Object newInstance(String text) throws Exception {
return new URI(text);
}
},
URL() {
- @Override Object newInstance(String text) throws Exception {
+ @Override
+ Object newInstance(String text) throws Exception {
return new URL(text);
}
};
+
abstract Object newInstance(String text) throws Exception;
}
- private static final String QUERY = "%E0%AE%A8%E0%AE%BE%E0%AE%AE%E0%AF%8D+%E0%AE%AE%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AE%BF%E0%AE%AF%E0%AE%AE%E0%AE%BE%E0%AE%A9%2C+%E0%AE%9A%E0%AF%81%E0%AE%B5%E0%AE%BE%E0%AE%B0%E0%AE%B8%E0%AF%8D%E0%AE%AF%E0%AE%AE%E0%AE%BE%E0%AE%A9+%E0%AE%87%E0%AE%B0%E0%AF%81%E0%AE%AA%E0%AF%8D%E0%AE%AA%E0%AF%87%E0%AE%BE%E0%AE%AE%E0%AF%8D%2C+%E0%AE%86%E0%AE%A9%E0%AE%BE%E0%AE%B2%E0%AF%8D+%E0%AE%9A%E0%AE%BF%E0%AE%B2+%E0%AE%A8%E0%AF%87%E0%AE%B0%E0%AE%99%E0%AF%8D%E0%AE%95%E0%AE%B3%E0%AE%BF%E0%AE%B2%E0%AF%8D+%E0%AE%9A%E0%AF%82%E0%AE%B4%E0%AF%8D%E0%AE%A8%E0%AE%BF%E0%AE%B2%E0%AF%88+%E0%AE%8F%E0%AE%B1%E0%AF%8D%E0%AE%AA%E0%AE%9F%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%8E%E0%AE%A9%E0%AF%8D%E0%AE%AA%E0%AE%A4%E0%AE%BE%E0%AE%B2%E0%AF%8D+%E0%AE%AA%E0%AE%A3%E0%AE%BF%E0%AE%AF%E0%AF%88%E0%AE%AF%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%B5%E0%AE%B2%E0%AE%BF+%E0%AE%85%E0%AE%B5%E0%AE%B0%E0%AF%88+%E0%AE%9A%E0%AE%BF%E0%AE%B2+%E0%AE%AA%E0%AF%86%E0%AE%B0%E0%AE%BF%E0%AE%AF+%E0%AE%95%E0%AF%86%E0%AE%BE%E0%AE%B3%E0%AF%8D%E0%AE%AE%E0%AF%81%E0%AE%A4%E0%AE%B2%E0%AF%8D+%E0%AE%AE%E0%AF%81%E0%AE%9F%E0%AE%BF%E0%AE%AF%E0%AF%81%E0%AE%AE%E0%AF%8D.+%E0%AE%85%E0%AE%A4%E0%AF%81+%E0%AE%9A%E0%AE%BF%E0%AE%B2+%E0%AE%A8%E0%AE%A9%E0%AF%8D%E0%AE%AE%E0%AF%88%E0%AE%95%E0%AE%B3%E0%AF%88+%E0%AE%AA%E0%AF%86%E0%AE%B1+%E0%AE%A4%E0%AE%B5%E0%AE%BF%E0%AE%B0%2C+%E0%AE%8E%E0%AE%AA%E0%AF%8D%E0%AE%AA%E0%AF%87%E0%AE%BE%E0%AE%A4%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%89%E0%AE%B4%E0%AF%88%E0%AE%95%E0%AF%8D%E0%AE%95+%E0%AE%89%E0%AE%9F%E0%AE%B1%E0%AF%8D%E0%AE%AA%E0%AE%AF%E0%AE%BF%E0%AE%B1%E0%AF%8D%E0%AE%9A%E0%AE%BF+%E0%AE%AE%E0%AF%87%E0%AE%B1%E0%AF%8D%E0%AE%95%E0%AF%86%E0%AE%BE%E0%AE%B3%E0%AF%8D%E0%AE%95%E0%AE%BF%E0%AE%B1%E0%AE%A4%E0%AF%81+%E0%AE%8E%E0%AE%99%E0%AF%8D%E0%AE%95%E0%AE%B3%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AF%81+%E0%AE%87%E0%AE%A4%E0%AF%81+%E0%AE%92%E0%AE%B0%E0%AF%81+%E0%AE%9A%E0%AE%BF%E0%AE%B1%E0%AE%BF%E0%AE%AF+%E0%AE%89%E0%AE%A4%E0%AE%BE%E0%AE%B0%E0%AE%A3%E0%AE%AE%E0%AF%8D%2C+%E0%AE%8E%E0%AE%9F%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95.+%E0%AE%B0%E0%AE%AF%E0%AE%BF%E0%AE%B2%E0%AF%8D+%E0%AE%8E%E0%AE%A8%E0%AF%8D%E0%AE%A4+%E0%AE%B5%E0%AE%BF%E0%AE%B3%E0%AF%88%E0%AE%B5%E0%AE%BE%E0%AE%95+%E0%AE%87%E0%AE%A9%E0%AF%8D%E0%AE%AA%E0%AE%AE%E0%AF%8D+%E0%AE%86%E0%AE%A9%E0%AF%8D%E0%AE%B2%E0%AF%88%E0%AE%A9%E0%AF%8D+%E0%AE%AA%E0%AE%AF%E0%AE%A9%E0%AF%8D%E0%AE%AA%E0%AE%BE%E0%AE%9F%E0%AF%81%E0%AE%95%E0%AE%B3%E0%AF%8D+%E0%AE%87%E0%AE%B0%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95+%E0%AE%B5%E0%AF%87%E0%AE%A3%E0%AF%8D%E0%AE%9F%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%A4%E0%AE%AF%E0%AE%BE%E0%AE%B0%E0%AE%BF%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%A4%E0%AE%B5%E0%AE%B1%E0%AF%81+%E0%AE%95%E0%AE%A3%E0%AF%8D%E0%AE%9F%E0%AF%81%E0%AE%AA%E0%AE%BF%E0%AE%9F%E0%AE%BF%E0%AE%95%E0%AF%8D%E0%AE%95+%E0%AE%B5%E0%AE%B0%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%A8%E0%AE%BE%E0%AE%AE%E0%AF%8D+%E0%AE%A4%E0%AE%B1%E0%AF%8D%E0%AE%AA%E0%AF%87%E0%AE%BE%E0%AE%A4%E0%AF%81+%E0%AE%87%E0%AE%B0%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AE%BF%E0%AE%B1%E0%AF%87%E0%AE%BE%E0%AE%AE%E0%AF%8D.+%E0%AE%87%E0%AE%A8%E0%AF%8D%E0%AE%A4+%E0%AE%A8%E0%AE%BF%E0%AE%95%E0%AE%B4%E0%AF%8D%E0%AE%B5%E0%AF%81%E0%AE%95%E0%AE%B3%E0%AE%BF%E0%AE%B2%E0%AF%8D+%E0%AE%9A%E0%AF%86%E0%AE%AF%E0%AF%8D%E0%AE%A4%E0%AE%AA%E0%AE%BF%E0%AE%A9%E0%AF%8D+%E0%AE%85%E0%AE%AE%E0%AF%88%E0%AE%AA%E0%AF%8D%E0%AE%AA%E0%AE%BF%E0%AE%A9%E0%AF%8D+%E0%AE%95%E0%AE%A3%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AF%81%2C+%E0%AE%85%E0%AE%B5%E0%AE%B0%E0%AF%8D%E0%AE%95%E0%AE%B3%E0%AF%8D+%E0%AE%A4%E0%AE%B5%E0%AE%B1%E0%AF%81+%E0%AE%B5%E0%AE%BF%E0%AE%9F%E0%AF%8D%E0%AE%9F%E0%AF%81+quae+%E0%AE%AA%E0%AE%9F%E0%AF%8D%E0%AE%9F%E0%AE%B1%E0%AF%88+%E0%AE%A8%E0%AF%80%E0%AE%99%E0%AF%8D%E0%AE%95%E0%AE%B3%E0%AF%8D+%E0%AE%AA%E0%AE%B0%E0%AE%BF%E0%AE%A8%E0%AF%8D%E0%AE%A4%E0%AF%81%E0%AE%B0%E0%AF%88%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AE%BF%E0%AE%B1%E0%AF%87%E0%AE%BE%E0%AE%AE%E0%AF%8D+%E0%AE%AE%E0%AF%86%E0%AE%A9%E0%AF%8D%E0%AE%AE%E0%AF%88%E0%AE%AF%E0%AE%BE%E0%AE%95+%E0%AE%AE%E0%AE%BE%E0%AE%B1%E0%AF%81%E0%AE%AE%E0%AF%8D";
+ private static final String QUERY =
+ "%E0%AE%A8%E0%AE%BE%E0%AE%AE%E0%AF%8D+%E0%AE%AE%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AE%BF%E0%AE%AF%E0%AE%AE%E0%AE%BE%E0%AE%A9%2C+%E0%AE%9A%E0%AF%81%E0%AE%B5%E0%AE%BE%E0%AE%B0%E0%AE%B8%E0%AF%8D%E0%AE%AF%E0%AE%AE%E0%AE%BE%E0%AE%A9+%E0%AE%87%E0%AE%B0%E0%AF%81%E0%AE%AA%E0%AF%8D%E0%AE%AA%E0%AF%87%E0%AE%BE%E0%AE%AE%E0%AF%8D%2C+%E0%AE%86%E0%AE%A9%E0%AE%BE%E0%AE%B2%E0%AF%8D+%E0%AE%9A%E0%AE%BF%E0%AE%B2+%E0%AE%A8%E0%AF%87%E0%AE%B0%E0%AE%99%E0%AF%8D%E0%AE%95%E0%AE%B3%E0%AE%BF%E0%AE%B2%E0%AF%8D+%E0%AE%9A%E0%AF%82%E0%AE%B4%E0%AF%8D%E0%AE%A8%E0%AE%BF%E0%AE%B2%E0%AF%88+%E0%AE%8F%E0%AE%B1%E0%AF%8D%E0%AE%AA%E0%AE%9F%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%8E%E0%AE%A9%E0%AF%8D%E0%AE%AA%E0%AE%A4%E0%AE%BE%E0%AE%B2%E0%AF%8D+%E0%AE%AA%E0%AE%A3%E0%AE%BF%E0%AE%AF%E0%AF%88%E0%AE%AF%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%B5%E0%AE%B2%E0%AE%BF+%E0%AE%85%E0%AE%B5%E0%AE%B0%E0%AF%88+%E0%AE%9A%E0%AE%BF%E0%AE%B2+%E0%AE%AA%E0%AF%86%E0%AE%B0%E0%AE%BF%E0%AE%AF+%E0%AE%95%E0%AF%86%E0%AE%BE%E0%AE%B3%E0%AF%8D%E0%AE%AE%E0%AF%81%E0%AE%A4%E0%AE%B2%E0%AF%8D+%E0%AE%AE%E0%AF%81%E0%AE%9F%E0%AE%BF%E0%AE%AF%E0%AF%81%E0%AE%AE%E0%AF%8D.+%E0%AE%85%E0%AE%A4%E0%AF%81+%E0%AE%9A%E0%AE%BF%E0%AE%B2+%E0%AE%A8%E0%AE%A9%E0%AF%8D%E0%AE%AE%E0%AF%88%E0%AE%95%E0%AE%B3%E0%AF%88+%E0%AE%AA%E0%AF%86%E0%AE%B1+%E0%AE%A4%E0%AE%B5%E0%AE%BF%E0%AE%B0%2C+%E0%AE%8E%E0%AE%AA%E0%AF%8D%E0%AE%AA%E0%AF%87%E0%AE%BE%E0%AE%A4%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%89%E0%AE%B4%E0%AF%88%E0%AE%95%E0%AF%8D%E0%AE%95+%E0%AE%89%E0%AE%9F%E0%AE%B1%E0%AF%8D%E0%AE%AA%E0%AE%AF%E0%AE%BF%E0%AE%B1%E0%AF%8D%E0%AE%9A%E0%AE%BF+%E0%AE%AE%E0%AF%87%E0%AE%B1%E0%AF%8D%E0%AE%95%E0%AF%86%E0%AE%BE%E0%AE%B3%E0%AF%8D%E0%AE%95%E0%AE%BF%E0%AE%B1%E0%AE%A4%E0%AF%81+%E0%AE%8E%E0%AE%99%E0%AF%8D%E0%AE%95%E0%AE%B3%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AF%81+%E0%AE%87%E0%AE%A4%E0%AF%81+%E0%AE%92%E0%AE%B0%E0%AF%81+%E0%AE%9A%E0%AE%BF%E0%AE%B1%E0%AE%BF%E0%AE%AF+%E0%AE%89%E0%AE%A4%E0%AE%BE%E0%AE%B0%E0%AE%A3%E0%AE%AE%E0%AF%8D%2C+%E0%AE%8E%E0%AE%9F%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95.+%E0%AE%B0%E0%AE%AF%E0%AE%BF%E0%AE%B2%E0%AF%8D+%E0%AE%8E%E0%AE%A8%E0%AF%8D%E0%AE%A4+%E0%AE%B5%E0%AE%BF%E0%AE%B3%E0%AF%88%E0%AE%B5%E0%AE%BE%E0%AE%95+%E0%AE%87%E0%AE%A9%E0%AF%8D%E0%AE%AA%E0%AE%AE%E0%AF%8D+%E0%AE%86%E0%AE%A9%E0%AF%8D%E0%AE%B2%E0%AF%88%E0%AE%A9%E0%AF%8D+%E0%AE%AA%E0%AE%AF%E0%AE%A9%E0%AF%8D%E0%AE%AA%E0%AE%BE%E0%AE%9F%E0%AF%81%E0%AE%95%E0%AE%B3%E0%AF%8D+%E0%AE%87%E0%AE%B0%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95+%E0%AE%B5%E0%AF%87%E0%AE%A3%E0%AF%8D%E0%AE%9F%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%A4%E0%AE%AF%E0%AE%BE%E0%AE%B0%E0%AE%BF%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%A4%E0%AE%B5%E0%AE%B1%E0%AF%81+%E0%AE%95%E0%AE%A3%E0%AF%8D%E0%AE%9F%E0%AF%81%E0%AE%AA%E0%AE%BF%E0%AE%9F%E0%AE%BF%E0%AE%95%E0%AF%8D%E0%AE%95+%E0%AE%B5%E0%AE%B0%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%A8%E0%AE%BE%E0%AE%AE%E0%AF%8D+%E0%AE%A4%E0%AE%B1%E0%AF%8D%E0%AE%AA%E0%AF%87%E0%AE%BE%E0%AE%A4%E0%AF%81+%E0%AE%87%E0%AE%B0%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AE%BF%E0%AE%B1%E0%AF%87%E0%AE%BE%E0%AE%AE%E0%AF%8D.+%E0%AE%87%E0%AE%A8%E0%AF%8D%E0%AE%A4+%E0%AE%A8%E0%AE%BF%E0%AE%95%E0%AE%B4%E0%AF%8D%E0%AE%B5%E0%AF%81%E0%AE%95%E0%AE%B3%E0%AE%BF%E0%AE%B2%E0%AF%8D+%E0%AE%9A%E0%AF%86%E0%AE%AF%E0%AF%8D%E0%AE%A4%E0%AE%AA%E0%AE%BF%E0%AE%A9%E0%AF%8D+%E0%AE%85%E0%AE%AE%E0%AF%88%E0%AE%AA%E0%AF%8D%E0%AE%AA%E0%AE%BF%E0%AE%A9%E0%AF%8D+%E0%AE%95%E0%AE%A3%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AF%81%2C+%E0%AE%85%E0%AE%B5%E0%AE%B0%E0%AF%8D%E0%AE%95%E0%AE%B3%E0%AF%8D+%E0%AE%A4%E0%AE%B5%E0%AE%B1%E0%AF%81+%E0%AE%B5%E0%AE%BF%E0%AE%9F%E0%AF%8D%E0%AE%9F%E0%AF%81+quae+%E0%AE%AA%E0%AE%9F%E0%AF%8D%E0%AE%9F%E0%AE%B1%E0%AF%88+%E0%AE%A8%E0%AF%80%E0%AE%99%E0%AF%8D%E0%AE%95%E0%AE%B3%E0%AF%8D+%E0%AE%AA%E0%AE%B0%E0%AE%BF%E0%AE%A8%E0%AF%8D%E0%AE%A4%E0%AF%81%E0%AE%B0%E0%AF%88%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AE%BF%E0%AE%B1%E0%AF%87%E0%AE%BE%E0%AE%AE%E0%AF%8D+%E0%AE%AE%E0%AF%86%E0%AE%A9%E0%AF%8D%E0%AE%AE%E0%AF%88%E0%AE%AF%E0%AE%BE%E0%AE%95+%E0%AE%AE%E0%AE%BE%E0%AE%B1%E0%AF%81%E0%AE%AE%E0%AF%8D";
- @Parameterized.Parameters(name = "mType({0})")
- public static Collection cases() {
+ public static Collection getCases() {
final List<Object[]> params = new ArrayList<>();
for (Type type : Type.values()) {
- params.add(new Object[]{type});
+ params.add(new Object[] {type});
}
return params;
}
- @Parameterized.Parameter(0)
- public Type mType;
-
Object mA1;
Object mA2;
Object mB1;
@@ -73,20 +74,13 @@
Object mC1;
Object mC2;
- @Before
- public void setUp() throws Exception {
- mA1 = mType.newInstance("https://mail.google.com/mail/u/0/?shva=1#inbox");
- mA2 = mType.newInstance("https://mail.google.com/mail/u/0/?shva=1#inbox");
- mB1 = mType.newInstance("http://developer.android.com/reference/java/net/URI.html");
- mB2 = mType.newInstance("http://developer.android.com/reference/java/net/URI.html");
-
- mC1 = mType.newInstance("http://developer.android.com/query?q=" + QUERY);
- // Replace the very last char.
- mC2 = mType.newInstance("http://developer.android.com/query?q=" + QUERY.substring(0, QUERY.length() - 3) + "%AF");
- }
-
@Test
- public void timeEquals() {
+ @Parameters(method = "getCases")
+ public void timeEquals(Type type) throws Exception {
+ mA1 = type.newInstance("https://mail.google.com/mail/u/0/?shva=1#inbox");
+ mA2 = type.newInstance("https://mail.google.com/mail/u/0/?shva=1#inbox");
+ mB1 = type.newInstance("http://developer.android.com/reference/java/net/URI.html");
+ mB2 = type.newInstance("http://developer.android.com/reference/java/net/URI.html");
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
mA1.equals(mB1);
@@ -96,7 +90,10 @@
}
@Test
- public void timeHashCode() {
+ @Parameters(method = "getCases")
+ public void timeHashCode(Type type) throws Exception {
+ mA1 = type.newInstance("https://mail.google.com/mail/u/0/?shva=1#inbox");
+ mB1 = type.newInstance("http://developer.android.com/reference/java/net/URI.html");
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
mA1.hashCode();
@@ -105,7 +102,15 @@
}
@Test
- public void timeEqualsWithHeavilyEscapedComponent() {
+ @Parameters(method = "getCases")
+ public void timeEqualsWithHeavilyEscapedComponent(Type type) throws Exception {
+ mC1 = type.newInstance("http://developer.android.com/query?q=" + QUERY);
+ // Replace the very last char.
+ mC2 =
+ type.newInstance(
+ "http://developer.android.com/query?q="
+ + QUERY.substring(0, QUERY.length() - 3)
+ + "%AF");
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
mC1.equals(mC2);
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/KeyPairGeneratorPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/KeyPairGeneratorPerfTest.java
index 6fe9059..80c4487 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/KeyPairGeneratorPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/KeyPairGeneratorPerfTest.java
@@ -20,26 +20,24 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
-import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class KeyPairGeneratorPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mAlgorithm={0}, mImplementation={1}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{Algorithm.RSA, Implementation.BouncyCastle},
@@ -48,12 +46,6 @@
});
}
- @Parameterized.Parameter(0)
- public Algorithm mAlgorithm;
-
- @Parameterized.Parameter(1)
- public Implementation mImplementation;
-
public enum Algorithm {
RSA,
DSA,
@@ -66,26 +58,25 @@
private String mGeneratorAlgorithm;
private KeyPairGenerator mGenerator;
- private SecureRandom mRandom;
- @Before
- public void setUp() throws Exception {
- this.mGeneratorAlgorithm = mAlgorithm.toString();
+ public void setUp(Algorithm algorithm, Implementation implementation) throws Exception {
+ this.mGeneratorAlgorithm = algorithm.toString();
final String provider;
- if (mImplementation == Implementation.BouncyCastle) {
+ if (implementation == Implementation.BouncyCastle) {
provider = "BC";
} else {
provider = "AndroidOpenSSL";
}
this.mGenerator = KeyPairGenerator.getInstance(mGeneratorAlgorithm, provider);
- this.mRandom = SecureRandom.getInstance("SHA1PRNG");
this.mGenerator.initialize(1024);
}
@Test
- public void time() throws Exception {
+ @Parameters(method = "getData")
+ public void time(Algorithm algorithm, Implementation implementation) throws Exception {
+ setUp(algorithm, implementation);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
KeyPair keyPair = mGenerator.generateKeyPair();
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/LoopingBackwardsPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/LoopingBackwardsPerfTest.java
index 414764d..c9b0cbe 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/LoopingBackwardsPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/LoopingBackwardsPerfTest.java
@@ -20,11 +20,12 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.util.Arrays;
import java.util.Collection;
@@ -34,36 +35,34 @@
*
* @author Kevin Bourrillion
*/
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class LoopingBackwardsPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mMax={0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(new Object[][] {{2}, {20}, {2000}, {20000000}});
}
- @Parameterized.Parameter(0)
- public int mMax;
-
@Test
- public void timeForwards() {
+ @Parameters(method = "getData")
+ public void timeForwards(int max) {
int fake = 0;
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- for (int j = 0; j < mMax; j++) {
+ for (int j = 0; j < max; j++) {
fake += j;
}
}
}
@Test
- public void timeBackwards() {
+ @Parameters(method = "getData")
+ public void timeBackwards(int max) {
int fake = 0;
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- for (int j = mMax - 1; j >= 0; j--) {
+ for (int j = max - 1; j >= 0; j--) {
fake += j;
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/MessageDigestPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/MessageDigestPerfTest.java
index 279681b..2dc947a 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/MessageDigestPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/MessageDigestPerfTest.java
@@ -20,24 +20,24 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class MessageDigestPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mAlgorithm={0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{Algorithm.MD5},
@@ -48,9 +48,6 @@
});
}
- @Parameterized.Parameter(0)
- public Algorithm mAlgorithm;
-
public String mProvider = "AndroidOpenSSL";
private static final int DATA_SIZE = 8192;
@@ -97,44 +94,44 @@
};
@Test
- public void time() throws Exception {
+ @Parameters(method = "getData")
+ public void time(Algorithm algorithm) throws Exception {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- MessageDigest digest =
- MessageDigest.getInstance(mAlgorithm.toString(), mProvider);
+ MessageDigest digest = MessageDigest.getInstance(algorithm.toString(), mProvider);
digest.update(DATA, 0, DATA_SIZE);
digest.digest();
}
}
@Test
- public void timeLargeArray() throws Exception {
+ @Parameters(method = "getData")
+ public void timeLargeArray(Algorithm algorithm) throws Exception {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- MessageDigest digest =
- MessageDigest.getInstance(mAlgorithm.toString(), mProvider);
+ MessageDigest digest = MessageDigest.getInstance(algorithm.toString(), mProvider);
digest.update(LARGE_DATA, 0, LARGE_DATA_SIZE);
digest.digest();
}
}
@Test
- public void timeSmallChunkOfLargeArray() throws Exception {
+ @Parameters(method = "getData")
+ public void timeSmallChunkOfLargeArray(Algorithm algorithm) throws Exception {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- MessageDigest digest =
- MessageDigest.getInstance(mAlgorithm.toString(), mProvider);
+ MessageDigest digest = MessageDigest.getInstance(algorithm.toString(), mProvider);
digest.update(LARGE_DATA, LARGE_DATA_SIZE / 2, DATA_SIZE);
digest.digest();
}
}
@Test
- public void timeSmallByteBuffer() throws Exception {
+ @Parameters(method = "getData")
+ public void timeSmallByteBuffer(Algorithm algorithm) throws Exception {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- MessageDigest digest =
- MessageDigest.getInstance(mAlgorithm.toString(), mProvider);
+ MessageDigest digest = MessageDigest.getInstance(algorithm.toString(), mProvider);
SMALL_BUFFER.position(0);
SMALL_BUFFER.limit(SMALL_BUFFER.capacity());
digest.update(SMALL_BUFFER);
@@ -143,11 +140,11 @@
}
@Test
- public void timeSmallDirectByteBuffer() throws Exception {
+ @Parameters(method = "getData")
+ public void timeSmallDirectByteBuffer(Algorithm algorithm) throws Exception {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- MessageDigest digest =
- MessageDigest.getInstance(mAlgorithm.toString(), mProvider);
+ MessageDigest digest = MessageDigest.getInstance(algorithm.toString(), mProvider);
SMALL_DIRECT_BUFFER.position(0);
SMALL_DIRECT_BUFFER.limit(SMALL_DIRECT_BUFFER.capacity());
digest.update(SMALL_DIRECT_BUFFER);
@@ -156,11 +153,11 @@
}
@Test
- public void timeLargeByteBuffer() throws Exception {
+ @Parameters(method = "getData")
+ public void timeLargeByteBuffer(Algorithm algorithm) throws Exception {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- MessageDigest digest =
- MessageDigest.getInstance(mAlgorithm.toString(), mProvider);
+ MessageDigest digest = MessageDigest.getInstance(algorithm.toString(), mProvider);
LARGE_BUFFER.position(0);
LARGE_BUFFER.limit(LARGE_BUFFER.capacity());
digest.update(LARGE_BUFFER);
@@ -169,11 +166,11 @@
}
@Test
- public void timeLargeDirectByteBuffer() throws Exception {
+ @Parameters(method = "getData")
+ public void timeLargeDirectByteBuffer(Algorithm algorithm) throws Exception {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- MessageDigest digest =
- MessageDigest.getInstance(mAlgorithm.toString(), mProvider);
+ MessageDigest digest = MessageDigest.getInstance(algorithm.toString(), mProvider);
LARGE_DIRECT_BUFFER.position(0);
LARGE_DIRECT_BUFFER.limit(LARGE_DIRECT_BUFFER.capacity());
digest.update(LARGE_DIRECT_BUFFER);
@@ -182,11 +179,11 @@
}
@Test
- public void timeSmallChunkOfLargeByteBuffer() throws Exception {
+ @Parameters(method = "getData")
+ public void timeSmallChunkOfLargeByteBuffer(Algorithm algorithm) throws Exception {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- MessageDigest digest =
- MessageDigest.getInstance(mAlgorithm.toString(), mProvider);
+ MessageDigest digest = MessageDigest.getInstance(algorithm.toString(), mProvider);
LARGE_BUFFER.position(LARGE_BUFFER.capacity() / 2);
LARGE_BUFFER.limit(LARGE_BUFFER.position() + DATA_SIZE);
digest.update(LARGE_BUFFER);
@@ -195,11 +192,11 @@
}
@Test
- public void timeSmallChunkOfLargeDirectByteBuffer() throws Exception {
+ @Parameters(method = "getData")
+ public void timeSmallChunkOfLargeDirectByteBuffer(Algorithm algorithm) throws Exception {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- MessageDigest digest =
- MessageDigest.getInstance(mAlgorithm.toString(), mProvider);
+ MessageDigest digest = MessageDigest.getInstance(algorithm.toString(), mProvider);
LARGE_DIRECT_BUFFER.position(LARGE_DIRECT_BUFFER.capacity() / 2);
LARGE_DIRECT_BUFFER.limit(LARGE_DIRECT_BUFFER.position() + DATA_SIZE);
digest.update(LARGE_DIRECT_BUFFER);
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/MutableIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/MutableIntPerfTest.java
index 37bd73c..d9d4bb5 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/MutableIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/MutableIntPerfTest.java
@@ -20,17 +20,18 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicInteger;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public final class MutableIntPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -96,29 +97,28 @@
abstract int timeGet(BenchmarkState state);
}
- @Parameters(name = "mKind={0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(new Object[][] {{Kind.ARRAY}, {Kind.ATOMIC}});
}
- @Parameterized.Parameter(0)
- public Kind mKind;
-
@Test
- public void timeCreate() {
+ @Parameters(method = "getData")
+ public void timeCreate(Kind kind) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- mKind.timeCreate(state);
+ kind.timeCreate(state);
}
@Test
- public void timeIncrement() {
+ @Parameters(method = "getData")
+ public void timeIncrement(Kind kind) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- mKind.timeIncrement(state);
+ kind.timeIncrement(state);
}
@Test
- public void timeGet() {
+ @Parameters(method = "getData")
+ public void timeGet(Kind kind) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- mKind.timeGet(state);
+ kind.timeGet(state);
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/PriorityQueuePerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/PriorityQueuePerfTest.java
index 8801a56..48450b4 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/PriorityQueuePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/PriorityQueuePerfTest.java
@@ -20,12 +20,12 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.util.ArrayList;
import java.util.Arrays;
@@ -35,13 +35,12 @@
import java.util.PriorityQueue;
import java.util.Random;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class PriorityQueuePerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mQueueSize={0}, mHitRate={1}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{100, 0},
@@ -62,26 +61,19 @@
});
}
- @Parameterized.Parameter(0)
- public int mQueueSize;
-
- @Parameterized.Parameter(1)
- public int mHitRate;
-
private PriorityQueue<Integer> mPq;
private PriorityQueue<Integer> mUsepq;
private List<Integer> mSeekElements;
private Random mRandom = new Random(189279387L);
- @Before
- public void setUp() throws Exception {
+ public void setUp(int queueSize, int hitRate) throws Exception {
mPq = new PriorityQueue<Integer>();
mUsepq = new PriorityQueue<Integer>();
mSeekElements = new ArrayList<Integer>();
List<Integer> allElements = new ArrayList<Integer>();
- int numShared = (int) (mQueueSize * ((double) mHitRate / 100));
- // the total number of elements we require to engineer a hit rate of mHitRate%
- int totalElements = 2 * mQueueSize - numShared;
+ int numShared = (int) (queueSize * ((double) hitRate / 100));
+ // the total number of elements we require to engineer a hit rate of hitRate%
+ int totalElements = 2 * queueSize - numShared;
for (int i = 0; i < totalElements; i++) {
allElements.add(i);
}
@@ -93,11 +85,11 @@
mSeekElements.add(allElements.get(i));
}
// add priority queue only elements (these won't be touched)
- for (int i = numShared; i < mQueueSize; i++) {
+ for (int i = numShared; i < queueSize; i++) {
mPq.add(allElements.get(i));
}
// add non-priority queue elements (these will be misses)
- for (int i = mQueueSize; i < totalElements; i++) {
+ for (int i = queueSize; i < totalElements; i++) {
mSeekElements.add(allElements.get(i));
}
mUsepq = new PriorityQueue<Integer>(mPq);
@@ -107,16 +99,18 @@
}
@Test
- public void timeRemove() {
+ @Parameters(method = "getData")
+ public void timeRemove(int queueSize, int hitRate) throws Exception {
+ setUp(queueSize, hitRate);
boolean fake = false;
int elementsSize = mSeekElements.size();
// At most allow the queue to empty 10%.
- int resizingThreshold = mQueueSize / 10;
+ int resizingThreshold = queueSize / 10;
int i = 0;
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
// Reset queue every so often. This will be called more often for smaller
- // mQueueSizes, but since a copy is linear, it will also cost proportionally
+ // queueSizes, but since a copy is linear, it will also cost proportionally
// less, and hopefully it will approximately balance out.
if (++i % resizingThreshold == 0) {
mUsepq = new PriorityQueue<Integer>(mPq);
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/SchemePrefixPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/SchemePrefixPerfTest.java
index 42dc581..5ad62de 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/SchemePrefixPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/SchemePrefixPerfTest.java
@@ -20,11 +20,12 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.util.Arrays;
import java.util.Collection;
@@ -32,7 +33,7 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public final class SchemePrefixPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -85,19 +86,16 @@
abstract String execute(String spec);
}
- @Parameters(name = "mStrategy={0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(new Object[][] {{Strategy.REGEX}, {Strategy.JAVA}});
}
- @Parameterized.Parameter(0)
- public Strategy mStrategy;
-
@Test
- public void timeSchemePrefix() {
+ @Parameters(method = "getData")
+ public void timeSchemePrefix(Strategy strategy) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mStrategy.execute("http://android.com");
+ strategy.execute("http://android.com");
}
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/SignaturePerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/SignaturePerfTest.java
index 96e7cb2..a9a0788 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/SignaturePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/SignaturePerfTest.java
@@ -19,12 +19,12 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
@@ -37,13 +37,12 @@
import java.util.Map;
/** Tests RSA and DSA mSignature creation and verification. */
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class SignaturePerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mAlgorithm={0}, mImplementation={1}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{Algorithm.MD5WithRSA, Implementation.OpenSSL},
@@ -55,12 +54,6 @@
});
}
- @Parameterized.Parameter(0)
- public Algorithm mAlgorithm;
-
- @Parameterized.Parameter(1)
- public Implementation mImplementation;
-
private static final int DATA_SIZE = 8192;
private static final byte[] DATA = new byte[DATA_SIZE];
@@ -94,9 +87,8 @@
private PrivateKey mPrivateKey;
private PublicKey mPublicKey;
- @Before
- public void setUp() throws Exception {
- this.mSignatureAlgorithm = mAlgorithm.toString();
+ public void setUp(Algorithm algorithm) throws Exception {
+ this.mSignatureAlgorithm = algorithm.toString();
String keyAlgorithm =
mSignatureAlgorithm.substring(
@@ -121,11 +113,13 @@
}
@Test
- public void timeSign() throws Exception {
+ @Parameters(method = "getData")
+ public void timeSign(Algorithm algorithm, Implementation implementation) throws Exception {
+ setUp(algorithm);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
Signature signer;
- switch (mImplementation) {
+ switch (implementation) {
case OpenSSL:
signer = Signature.getInstance(mSignatureAlgorithm, "AndroidOpenSSL");
break;
@@ -133,7 +127,7 @@
signer = Signature.getInstance(mSignatureAlgorithm, "BC");
break;
default:
- throw new RuntimeException(mImplementation.toString());
+ throw new RuntimeException(implementation.toString());
}
signer.initSign(mPrivateKey);
signer.update(DATA);
@@ -142,11 +136,13 @@
}
@Test
- public void timeVerify() throws Exception {
+ @Parameters(method = "getData")
+ public void timeVerify(Algorithm algorithm, Implementation implementation) throws Exception {
+ setUp(algorithm);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
Signature verifier;
- switch (mImplementation) {
+ switch (implementation) {
case OpenSSL:
verifier = Signature.getInstance(mSignatureAlgorithm, "AndroidOpenSSL");
break;
@@ -154,7 +150,7 @@
verifier = Signature.getInstance(mSignatureAlgorithm, "BC");
break;
default:
- throw new RuntimeException(mImplementation.toString());
+ throw new RuntimeException(implementation.toString());
}
verifier.initVerify(mPublicKey);
verifier.update(DATA);
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/StringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/StringPerfTest.java
index 02194b1..36db014 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/StringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/StringPerfTest.java
@@ -20,16 +20,17 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.util.Arrays;
import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class StringPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -46,8 +47,7 @@
}
}
- @Parameters(name = "mStringLengths={0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{StringLengths.EIGHT_KI},
@@ -57,9 +57,6 @@
});
}
- @Parameterized.Parameter(0)
- public StringLengths mStringLengths;
-
private static String makeString(int length) {
StringBuilder result = new StringBuilder(length);
for (int i = 0; i < length; ++i) {
@@ -69,10 +66,11 @@
}
@Test
- public void timeHashCode() {
+ @Parameters(method = "getData")
+ public void timeHashCode(StringLengths stringLengths) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mStringLengths.mValue.hashCode();
+ stringLengths.mValue.hashCode();
}
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/StringReplaceAllPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/StringReplaceAllPerfTest.java
index b0d1ee4..5b4423a 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/StringReplaceAllPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/StringReplaceAllPerfTest.java
@@ -20,16 +20,17 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.util.Arrays;
import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class StringReplaceAllPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -69,8 +70,7 @@
return stringBuilder.toString();
}
- @Parameters(name = "mStringLengths={0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{StringLengths.BOOT_IMAGE},
@@ -82,30 +82,30 @@
});
}
- @Parameterized.Parameter(0)
- public StringLengths mStringLengths;
-
@Test
- public void timeReplaceAllTrivialPatternNonExistent() {
+ @Parameters(method = "getData")
+ public void timeReplaceAllTrivialPatternNonExistent(StringLengths stringLengths) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mStringLengths.mValue.replaceAll("fish", "0");
+ stringLengths.mValue.replaceAll("fish", "0");
}
}
@Test
- public void timeReplaceTrivialPatternAllRepeated() {
+ @Parameters(method = "getData")
+ public void timeReplaceTrivialPatternAllRepeated(StringLengths stringLengths) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mStringLengths.mValue.replaceAll("jklm", "0");
+ stringLengths.mValue.replaceAll("jklm", "0");
}
}
@Test
- public void timeReplaceAllTrivialPatternSingleOccurrence() {
+ @Parameters(method = "getData")
+ public void timeReplaceAllTrivialPatternSingleOccurrence(StringLengths stringLengths) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mStringLengths.mValue.replaceAll("qrst", "0");
+ stringLengths.mValue.replaceAll("qrst", "0");
}
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/StringReplacePerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/StringReplacePerfTest.java
index d2e657a..4d5c792 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/StringReplacePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/StringReplacePerfTest.java
@@ -20,16 +20,17 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.util.Arrays;
import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class StringReplacePerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -64,8 +65,7 @@
return stringBuilder.toString();
}
- @Parameters(name = "mStringLengths={0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{StringLengths.EMPTY},
@@ -76,54 +76,57 @@
});
}
- @Parameterized.Parameter(0)
- public StringLengths mStringLengths;
-
@Test
- public void timeReplaceCharNonExistent() {
+ @Parameters(method = "getData")
+ public void timeReplaceCharNonExistent(StringLengths stringLengths) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mStringLengths.mValue.replace('z', '0');
+ stringLengths.mValue.replace('z', '0');
}
}
@Test
- public void timeReplaceCharRepeated() {
+ @Parameters(method = "getData")
+ public void timeReplaceCharRepeated(StringLengths stringLengths) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mStringLengths.mValue.replace('a', '0');
+ stringLengths.mValue.replace('a', '0');
}
}
@Test
- public void timeReplaceSingleChar() {
+ @Parameters(method = "getData")
+ public void timeReplaceSingleChar(StringLengths stringLengths) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mStringLengths.mValue.replace('q', '0');
+ stringLengths.mValue.replace('q', '0');
}
}
@Test
- public void timeReplaceSequenceNonExistent() {
+ @Parameters(method = "getData")
+ public void timeReplaceSequenceNonExistent(StringLengths stringLengths) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mStringLengths.mValue.replace("fish", "0");
+ stringLengths.mValue.replace("fish", "0");
}
}
@Test
- public void timeReplaceSequenceRepeated() {
+ @Parameters(method = "getData")
+ public void timeReplaceSequenceRepeated(StringLengths stringLengths) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mStringLengths.mValue.replace("jklm", "0");
+ stringLengths.mValue.replace("jklm", "0");
}
}
@Test
- public void timeReplaceSingleSequence() {
+ @Parameters(method = "getData")
+ public void timeReplaceSingleSequence(StringLengths stringLengths) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mStringLengths.mValue.replace("qrst", "0");
+ stringLengths.mValue.replace("qrst", "0");
}
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/StringToBytesPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/StringToBytesPerfTest.java
index 1efc188..c004d95 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/StringToBytesPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/StringToBytesPerfTest.java
@@ -20,17 +20,18 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class StringToBytesPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -53,8 +54,7 @@
}
}
- @Parameters(name = "mStringLengths={0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{StringLengths.EMPTY},
@@ -69,9 +69,6 @@
});
}
- @Parameterized.Parameter(0)
- public StringLengths mStringLengths;
-
private static String makeString(int length) {
char[] chars = new char[length];
for (int i = 0; i < length; ++i) {
@@ -89,26 +86,29 @@
}
@Test
- public void timeGetBytesUtf8() {
+ @Parameters(method = "getData")
+ public void timeGetBytesUtf8(StringLengths stringLengths) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mStringLengths.mValue.getBytes(StandardCharsets.UTF_8);
+ stringLengths.mValue.getBytes(StandardCharsets.UTF_8);
}
}
@Test
- public void timeGetBytesIso88591() {
+ @Parameters(method = "getData")
+ public void timeGetBytesIso88591(StringLengths stringLengths) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mStringLengths.mValue.getBytes(StandardCharsets.ISO_8859_1);
+ stringLengths.mValue.getBytes(StandardCharsets.ISO_8859_1);
}
}
@Test
- public void timeGetBytesAscii() {
+ @Parameters(method = "getData")
+ public void timeGetBytesAscii(StringLengths stringLengths) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mStringLengths.mValue.getBytes(StandardCharsets.US_ASCII);
+ stringLengths.mValue.getBytes(StandardCharsets.US_ASCII);
}
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/StringToRealPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/StringToRealPerfTest.java
index b01948a..15516fc 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/StringToRealPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/StringToRealPerfTest.java
@@ -20,22 +20,22 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.util.Arrays;
import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class StringToRealPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mString={0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{"NaN"},
@@ -49,22 +49,21 @@
});
}
- @Parameterized.Parameter(0)
- public String mString;
-
@Test
- public void timeFloat_parseFloat() {
+ @Parameters(method = "getData")
+ public void timeFloat_parseFloat(String string) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- Float.parseFloat(mString);
+ Float.parseFloat(string);
}
}
@Test
- public void timeDouble_parseDouble() {
+ @Parameters(method = "getData")
+ public void timeDouble_parseDouble(String string) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- Double.parseDouble(mString);
+ Double.parseDouble(string);
}
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/XMLEntitiesPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/XMLEntitiesPerfTest.java
index 2ea834d..ae1e8bc 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/XMLEntitiesPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/XMLEntitiesPerfTest.java
@@ -20,12 +20,12 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import org.xml.sax.InputSource;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserFactory;
@@ -38,13 +38,12 @@
import javax.xml.parsers.DocumentBuilderFactory;
// http://code.google.com/p/android/issues/detail?id=18102
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public final class XMLEntitiesPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mLength={0}, mEntityFraction={1}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{10, 0},
@@ -59,29 +58,22 @@
});
}
- @Parameterized.Parameter(0)
- public int mLength;
-
- @Parameterized.Parameter(1)
- public float mEntityFraction;
-
private XmlPullParserFactory mXmlPullParserFactory;
private DocumentBuilderFactory mDocumentBuilderFactory;
/** a string like {@code <doc>&&++</doc>}. */
private String mXml;
- @Before
- public void setUp() throws Exception {
+ public void setUp(int length, float entityFraction) throws Exception {
mXmlPullParserFactory = XmlPullParserFactory.newInstance();
mDocumentBuilderFactory = DocumentBuilderFactory.newInstance();
StringBuilder xmlBuilder = new StringBuilder();
xmlBuilder.append("<doc>");
- for (int i = 0; i < (mLength * mEntityFraction); i++) {
+ for (int i = 0; i < (length * entityFraction); i++) {
xmlBuilder.append("&");
}
- while (xmlBuilder.length() < mLength) {
+ while (xmlBuilder.length() < length) {
xmlBuilder.append("+");
}
xmlBuilder.append("</doc>");
@@ -89,7 +81,9 @@
}
@Test
- public void timeXmlParser() throws Exception {
+ @Parameters(method = "getData")
+ public void timeXmlParser(int length, float entityFraction) throws Exception {
+ setUp(length, entityFraction);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
XmlPullParser parser = mXmlPullParserFactory.newPullParser();
@@ -101,7 +95,9 @@
}
@Test
- public void timeDocumentBuilder() throws Exception {
+ @Parameters(method = "getData")
+ public void timeDocumentBuilder(int length, float entityFraction) throws Exception {
+ setUp(length, entityFraction);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
DocumentBuilder documentBuilder = mDocumentBuilderFactory.newDocumentBuilder();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 20bca35..52dc01b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -460,14 +460,14 @@
if (mPowerManager != null && mPowerManager.isDeviceIdleMode()) {
synchronized (mLock) {
stopUnexemptedJobsForDoze();
- stopLongRunningJobsLocked("deep doze");
+ stopOvertimeJobsLocked("deep doze");
}
}
break;
case PowerManager.ACTION_POWER_SAVE_MODE_CHANGED:
if (mPowerManager != null && mPowerManager.isPowerSaveMode()) {
synchronized (mLock) {
- stopLongRunningJobsLocked("battery saver");
+ stopOvertimeJobsLocked("battery saver");
}
}
break;
@@ -555,7 +555,7 @@
* execution guarantee.
*/
@GuardedBy("mLock")
- boolean isJobLongRunningLocked(@NonNull JobStatus job) {
+ boolean isJobInOvertimeLocked(@NonNull JobStatus job) {
if (!mRunningJobs.contains(job)) {
return false;
}
@@ -1043,7 +1043,7 @@
}
@GuardedBy("mLock")
- private void stopLongRunningJobsLocked(@NonNull String debugReason) {
+ private void stopOvertimeJobsLocked(@NonNull String debugReason) {
for (int i = 0; i < mActiveServices.size(); ++i) {
final JobServiceContext jsc = mActiveServices.get(i);
final JobStatus jobStatus = jsc.getRunningJobLocked();
@@ -1060,7 +1060,7 @@
* restricted by the given {@link JobRestriction}.
*/
@GuardedBy("mLock")
- void maybeStopLongRunningJobsLocked(@NonNull JobRestriction restriction) {
+ void maybeStopOvertimeJobsLocked(@NonNull JobRestriction restriction) {
for (int i = mActiveServices.size() - 1; i >= 0; --i) {
final JobServiceContext jsc = mActiveServices.get(i);
final JobStatus jobStatus = jsc.getRunningJobLocked();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index d28ebde..6375d0d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1870,10 +1870,10 @@
return mConcurrencyManager.isJobRunningLocked(job);
}
- /** @see JobConcurrencyManager#isJobLongRunningLocked(JobStatus) */
+ /** @see JobConcurrencyManager#isJobInOvertimeLocked(JobStatus) */
@GuardedBy("mLock")
- public boolean isLongRunningLocked(JobStatus job) {
- return mConcurrencyManager.isJobLongRunningLocked(job);
+ public boolean isJobInOvertimeLocked(JobStatus job) {
+ return mConcurrencyManager.isJobInOvertimeLocked(job);
}
private void noteJobPending(JobStatus job) {
@@ -2155,11 +2155,11 @@
@Override
public void onRestrictionStateChanged(@NonNull JobRestriction restriction,
- boolean stopLongRunningJobs) {
+ boolean stopOvertimeJobs) {
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
- if (stopLongRunningJobs) {
+ if (stopOvertimeJobs) {
synchronized (mLock) {
- mConcurrencyManager.maybeStopLongRunningJobsLocked(restriction);
+ mConcurrencyManager.maybeStopOvertimeJobsLocked(restriction);
}
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java b/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java
index d7bd030..554f152 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java
@@ -41,11 +41,11 @@
* Called by a {@link com.android.server.job.restrictions.JobRestriction} to notify the
* JobScheduler that it should check on the state of all jobs.
*
- * @param stopLongRunningJobs Whether to stop any jobs that have run for more than their minimum
- * execution guarantee and are restricted by the changed restriction
+ * @param stopOvertimeJobs Whether to stop any jobs that have run for more than their minimum
+ * execution guarantee and are restricted by the changed restriction
*/
void onRestrictionStateChanged(@NonNull JobRestriction restriction,
- boolean stopLongRunningJobs);
+ boolean stopOvertimeJobs);
/**
* Called by the controller to notify the JobManager that regardless of the state of the task,
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
index a007a69..ca2fd60 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
@@ -90,11 +90,11 @@
final int priority = job.getEffectivePriority();
if (mThermalStatus >= HIGHER_PRIORITY_THRESHOLD) {
// For moderate throttling, only let expedited jobs and high priority regular jobs that
- // haven't been running for long run.
+ // haven't been running for a long time run.
return !job.shouldTreatAsExpeditedJob()
&& !(priority == JobInfo.PRIORITY_HIGH
&& mService.isCurrentlyRunningLocked(job)
- && !mService.isLongRunningLocked(job));
+ && !mService.isJobInOvertimeLocked(job));
}
if (mThermalStatus >= LOW_PRIORITY_THRESHOLD) {
// For light throttling, throttle all min priority jobs and all low priority jobs that
@@ -102,7 +102,7 @@
return priority == JobInfo.PRIORITY_MIN
|| (priority == JobInfo.PRIORITY_LOW
&& (!mService.isCurrentlyRunningLocked(job)
- || mService.isLongRunningLocked(job)));
+ || mService.isJobInOvertimeLocked(job)));
}
return false;
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 40e0c5b..218d7bd 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -172,6 +172,7 @@
field public static final String REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE = "android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE";
field public static final String REQUEST_PASSWORD_COMPLEXITY = "android.permission.REQUEST_PASSWORD_COMPLEXITY";
field @Deprecated public static final String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
+ field public static final String RUN_LONG_JOBS = "android.permission.RUN_LONG_JOBS";
field public static final String SCHEDULE_EXACT_ALARM = "android.permission.SCHEDULE_EXACT_ALARM";
field public static final String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE";
field public static final String SEND_SMS = "android.permission.SEND_SMS";
@@ -19497,7 +19498,7 @@
method public boolean hasSatelliteBlocklist();
method public boolean hasSatellitePvt();
method public boolean hasScheduling();
- method public boolean hasSingleShot();
+ method public boolean hasSingleShotFix();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssCapabilities> CREATOR;
}
@@ -19529,7 +19530,7 @@
method @NonNull public android.location.GnssCapabilities.Builder setHasSatelliteBlocklist(boolean);
method @NonNull public android.location.GnssCapabilities.Builder setHasSatellitePvt(boolean);
method @NonNull public android.location.GnssCapabilities.Builder setHasScheduling(boolean);
- method @NonNull public android.location.GnssCapabilities.Builder setHasSingleShot(boolean);
+ method @NonNull public android.location.GnssCapabilities.Builder setHasSingleShotFix(boolean);
}
public final class GnssClock implements android.os.Parcelable {
@@ -19727,7 +19728,7 @@
method public int getConstellationType(@IntRange(from=0) int);
method @FloatRange(from=0xffffffa6, to=90) public float getElevationDegrees(@IntRange(from=0) int);
method @IntRange(from=0) public int getSatelliteCount();
- method @IntRange(from=1, to=200) public int getSvid(@IntRange(from=0) int);
+ method @IntRange(from=1, to=206) public int getSvid(@IntRange(from=0) int);
method public boolean hasAlmanacData(@IntRange(from=0) int);
method public boolean hasBasebandCn0DbHz(@IntRange(from=0) int);
method public boolean hasCarrierFrequencyHz(@IntRange(from=0) int);
@@ -44006,9 +44007,10 @@
field public static final long NETWORK_TYPE_BITMASK_HSPA = 512L; // 0x200L
field public static final long NETWORK_TYPE_BITMASK_HSPAP = 16384L; // 0x4000L
field public static final long NETWORK_TYPE_BITMASK_HSUPA = 256L; // 0x100L
+ field public static final long NETWORK_TYPE_BITMASK_IDEN = 1024L; // 0x400L
field public static final long NETWORK_TYPE_BITMASK_IWLAN = 131072L; // 0x20000L
field public static final long NETWORK_TYPE_BITMASK_LTE = 4096L; // 0x1000L
- field public static final long NETWORK_TYPE_BITMASK_LTE_CA = 262144L; // 0x40000L
+ field @Deprecated public static final long NETWORK_TYPE_BITMASK_LTE_CA = 262144L; // 0x40000L
field public static final long NETWORK_TYPE_BITMASK_NR = 524288L; // 0x80000L
field public static final long NETWORK_TYPE_BITMASK_TD_SCDMA = 65536L; // 0x10000L
field public static final long NETWORK_TYPE_BITMASK_UMTS = 4L; // 0x4L
@@ -47431,6 +47433,7 @@
field public static final int DENSITY_420 = 420; // 0x1a4
field public static final int DENSITY_440 = 440; // 0x1b8
field public static final int DENSITY_450 = 450; // 0x1c2
+ field public static final int DENSITY_520 = 520; // 0x208
field public static final int DENSITY_560 = 560; // 0x230
field public static final int DENSITY_600 = 600; // 0x258
field public static final int DENSITY_DEFAULT = 160; // 0xa0
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index e890005..88efcce 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -301,10 +301,6 @@
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void reportNetworkInterfaceForTransports(@NonNull String, @NonNull int[]) throws java.lang.RuntimeException;
}
- public class Binder implements android.os.IBinder {
- method public final void markVintfStability();
- }
-
public class BluetoothServiceManager {
method @NonNull public android.os.BluetoothServiceManager.ServiceRegisterer getBluetoothManagerServiceRegisterer();
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 74288ae..a382ecf 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -9351,6 +9351,7 @@
public class Binder implements android.os.IBinder {
method public int handleShellCommand(@NonNull android.os.ParcelFileDescriptor, @NonNull android.os.ParcelFileDescriptor, @NonNull android.os.ParcelFileDescriptor, @NonNull String[]);
+ method public final void markVintfStability();
method public static void setProxyTransactListener(@Nullable android.os.Binder.ProxyTransactListener);
}
@@ -13397,7 +13398,7 @@
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int[] getCompleteActiveSubscriptionIdList();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEnabledSubscriptionId(int);
method @NonNull public static android.content.res.Resources getResourcesForSubId(@NonNull android.content.Context, int);
- method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION) public android.os.UserHandle getUserHandle(int);
+ method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION) public android.os.UserHandle getSubscriptionUserHandle(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isSubscriptionEnabled(int);
method public void requestEmbeddedSubscriptionInfoListRefresh();
method public void requestEmbeddedSubscriptionInfoListRefresh(int);
@@ -13407,8 +13408,8 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDefaultVoiceSubscriptionId(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setPreferredDataSubscriptionId(int, boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setSubscriptionEnabled(int, boolean);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION) public void setSubscriptionUserHandle(int, @Nullable android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUiccApplicationsEnabled(int, boolean);
- method @RequiresPermission(android.Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION) public void setUserHandle(int, @Nullable android.os.UserHandle);
field @RequiresPermission(android.Manifest.permission.MANAGE_SUBSCRIPTION_PLANS) public static final String ACTION_SUBSCRIPTION_PLANS_CHANGED = "android.telephony.action.SUBSCRIPTION_PLANS_CHANGED";
field @NonNull public static final android.net.Uri ADVANCED_CALLING_ENABLED_CONTENT_URI;
field @NonNull public static final android.net.Uri CROSS_SIM_ENABLED_CONTENT_URI;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index d1275f6..1b972e0 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1353,9 +1353,16 @@
public static final int OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO =
AppProtoEnums.APP_OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO;
+ /**
+ * App can schedule long running jobs.
+ *
+ * @hide
+ */
+ public static final int OP_RUN_LONG_JOBS = AppProtoEnums.APP_OP_RUN_LONG_JOBS;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 122;
+ public static final int _NUM_OP = 123;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1839,6 +1846,13 @@
public static final String OPSTR_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO =
"android:receive_explicit_user_interaction_audio";
+ /**
+ * App can schedule long running jobs.
+ *
+ * @hide
+ */
+ public static final String OPSTR_RUN_LONG_JOBS = "android:run_long_jobs";
+
/** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
/** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -1933,6 +1947,7 @@
OP_SCHEDULE_EXACT_ALARM,
OP_MANAGE_MEDIA,
OP_TURN_SCREEN_ON,
+ OP_RUN_LONG_JOBS,
};
static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2312,7 +2327,9 @@
new AppOpInfo.Builder(OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO,
OPSTR_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO,
"RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO").setDefaultMode(
- AppOpsManager.MODE_ALLOWED).build()
+ AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_RUN_LONG_JOBS, OPSTR_RUN_LONG_JOBS, "RUN_LONG_JOBS")
+ .setPermission(Manifest.permission.RUN_LONG_JOBS).build()
};
/**
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 08a6b8c..aaa3d21 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -173,6 +173,7 @@
import android.os.IUserManager;
import android.os.IncidentManager;
import android.os.PerformanceHintManager;
+import android.os.PermissionEnforcer;
import android.os.PowerManager;
import android.os.RecoverySystem;
import android.os.ServiceManager;
@@ -1366,6 +1367,14 @@
return new PermissionCheckerManager(ctx.getOuterContext());
}});
+ registerService(Context.PERMISSION_ENFORCER_SERVICE, PermissionEnforcer.class,
+ new CachedServiceFetcher<PermissionEnforcer>() {
+ @Override
+ public PermissionEnforcer createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ return new PermissionEnforcer(ctx.getOuterContext());
+ }});
+
registerService(Context.DYNAMIC_SYSTEM_SERVICE, DynamicSystemManager.class,
new CachedServiceFetcher<DynamicSystemManager>() {
@Override
diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.java b/core/java/android/app/backup/BackupRestoreEventLogger.java
index b789b38..760c6f0 100644
--- a/core/java/android/app/backup/BackupRestoreEventLogger.java
+++ b/core/java/android/app/backup/BackupRestoreEventLogger.java
@@ -19,10 +19,15 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.util.Slog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Collections;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -38,6 +43,8 @@
* @hide
*/
public class BackupRestoreEventLogger {
+ private static final String TAG = "BackupRestoreEventLogger";
+
/**
* Max number of unique data types for which an instance of this logger can store info. Attempts
* to use more distinct data type values will be rejected.
@@ -72,6 +79,8 @@
public @interface BackupRestoreError {}
private final int mOperationType;
+ private final Map<String, DataTypeResult> mResults = new HashMap<>();
+ private final MessageDigest mHashDigest;
/**
* @param operationType type of the operation for which logging will be performed. See
@@ -81,6 +90,14 @@
*/
public BackupRestoreEventLogger(@OperationType int operationType) {
mOperationType = operationType;
+
+ MessageDigest hashDigest = null;
+ try {
+ hashDigest = MessageDigest.getInstance("SHA-256");
+ } catch (NoSuchAlgorithmException e) {
+ Slog.w("Couldn't create MessageDigest for hash computation", e);
+ }
+ mHashDigest = hashDigest;
}
/**
@@ -98,7 +115,7 @@
* @return boolean, indicating whether the log has been accepted.
*/
public boolean logItemsBackedUp(@NonNull @BackupRestoreDataType String dataType, int count) {
- return true;
+ return logSuccess(OperationType.BACKUP, dataType, count);
}
/**
@@ -118,7 +135,7 @@
*/
public boolean logItemsBackupFailed(@NonNull @BackupRestoreDataType String dataType, int count,
@Nullable @BackupRestoreError String error) {
- return true;
+ return logFailure(OperationType.BACKUP, dataType, count, error);
}
/**
@@ -139,7 +156,7 @@
*/
public boolean logBackupMetaData(@NonNull @BackupRestoreDataType String dataType,
@NonNull String metaData) {
- return true;
+ return logMetaData(OperationType.BACKUP, dataType, metaData);
}
/**
@@ -159,7 +176,7 @@
* @return boolean, indicating whether the log has been accepted.
*/
public boolean logItemsRestored(@NonNull @BackupRestoreDataType String dataType, int count) {
- return true;
+ return logSuccess(OperationType.RESTORE, dataType, count);
}
/**
@@ -181,7 +198,7 @@
*/
public boolean logItemsRestoreFailed(@NonNull @BackupRestoreDataType String dataType, int count,
@Nullable @BackupRestoreError String error) {
- return true;
+ return logFailure(OperationType.RESTORE, dataType, count, error);
}
/**
@@ -204,7 +221,7 @@
*/
public boolean logRestoreMetadata(@NonNull @BackupRestoreDataType String dataType,
@NonNull String metadata) {
- return true;
+ return logMetaData(OperationType.RESTORE, dataType, metadata);
}
/**
@@ -214,7 +231,7 @@
* @hide
*/
public List<DataTypeResult> getLoggingResults() {
- return Collections.emptyList();
+ return new ArrayList<>(mResults.values());
}
/**
@@ -227,22 +244,97 @@
return mOperationType;
}
+ private boolean logSuccess(@OperationType int operationType,
+ @BackupRestoreDataType String dataType, int count) {
+ DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
+ if (dataTypeResult == null) {
+ return false;
+ }
+
+ dataTypeResult.mSuccessCount += count;
+ mResults.put(dataType, dataTypeResult);
+
+ return true;
+ }
+
+ private boolean logFailure(@OperationType int operationType,
+ @NonNull @BackupRestoreDataType String dataType, int count,
+ @Nullable @BackupRestoreError String error) {
+ DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
+ if (dataTypeResult == null) {
+ return false;
+ }
+
+ dataTypeResult.mFailCount += count;
+ if (error != null) {
+ dataTypeResult.mErrors.merge(error, count, Integer::sum);
+ }
+
+ return true;
+ }
+
+ private boolean logMetaData(@OperationType int operationType,
+ @NonNull @BackupRestoreDataType String dataType, @NonNull String metaData) {
+ if (mHashDigest == null) {
+ return false;
+ }
+ DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
+ if (dataTypeResult == null) {
+ return false;
+ }
+
+ dataTypeResult.mMetadataHash = getMetaDataHash(metaData);
+
+ return true;
+ }
+
+ /**
+ * Get the result container for the given data type.
+ *
+ * @return {@code DataTypeResult} object corresponding to the given {@code dataType} or
+ * {@code null} if the logger can't accept logs for the given data type.
+ */
+ @Nullable
+ private DataTypeResult getDataTypeResult(@OperationType int operationType,
+ @BackupRestoreDataType String dataType) {
+ if (operationType != mOperationType) {
+ // Operation type for which we're trying to record logs doesn't match the operation
+ // type for which this logger instance was created.
+ Slog.d(TAG, "Operation type mismatch: logger created for " + mOperationType
+ + ", trying to log for " + operationType);
+ return null;
+ }
+
+ if (!mResults.containsKey(dataType)) {
+ if (mResults.keySet().size() == DATA_TYPES_ALLOWED) {
+ // This is a new data type and we're already at capacity.
+ Slog.d(TAG, "Logger is full, ignoring new data type");
+ return null;
+ }
+
+ mResults.put(dataType, new DataTypeResult(dataType));
+ }
+
+ return mResults.get(dataType);
+ }
+
+ private byte[] getMetaDataHash(String metaData) {
+ return mHashDigest.digest(metaData.getBytes(StandardCharsets.UTF_8));
+ }
+
/**
* Encapsulate logging results for a single data type.
*/
public static class DataTypeResult {
@BackupRestoreDataType
private final String mDataType;
- private final int mSuccessCount;
- private final Map<String, Integer> mErrors;
- private final byte[] mMetadataHash;
+ private int mSuccessCount;
+ private int mFailCount;
+ private final Map<String, Integer> mErrors = new HashMap<>();
+ private byte[] mMetadataHash;
- public DataTypeResult(String dataType, int successCount,
- Map<String, Integer> errors, byte[] metadataHash) {
+ public DataTypeResult(String dataType) {
mDataType = dataType;
- mSuccessCount = successCount;
- mErrors = errors;
- mMetadataHash = metadataHash;
}
@NonNull
@@ -260,6 +352,13 @@
}
/**
+ * @return number of items of the given data type that have failed to back up or restore.
+ */
+ public int getFailCount() {
+ return mFailCount;
+ }
+
+ /**
* @return mapping of {@link BackupRestoreError} to the count of items that are affected by
* the error.
*/
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index d65210b..1df0fa8 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5142,6 +5142,14 @@
public static final String PERMISSION_CHECKER_SERVICE = "permission_checker";
/**
+ * Official published name of the (internal) permission enforcer service.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ public static final String PERMISSION_ENFORCER_SERVICE = "permission_enforcer";
+
+ /**
* Use with {@link #getSystemService(String) to retrieve an
* {@link android.apphibernation.AppHibernationManager}} for
* communicating with the hibernation service.
@@ -5194,6 +5202,15 @@
public static final String DROPBOX_SERVICE = "dropbox";
/**
+ * System service name for BackgroundInstallControlService. This service supervises the MBAs
+ * on device and provides the related metadata of the MBAs.
+ *
+ * @hide
+ */
+ @SuppressLint("ServiceName")
+ public static final String BACKGROUND_INSTALL_CONTROL_SERVICE = "background_install_control";
+
+ /**
* System service name for BinaryTransparencyService. This is used to retrieve measurements
* pertaining to various pre-installed and system binaries on device for the purposes of
* providing transparency to the user.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt b/core/java/android/content/pm/IBackgroundInstallControlService.aidl
similarity index 72%
copy from packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
copy to core/java/android/content/pm/IBackgroundInstallControlService.aidl
index 581dafa3..c8e7cae 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
+++ b/core/java/android/content/pm/IBackgroundInstallControlService.aidl
@@ -14,10 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.domain.model
+package android.content.pm;
-/** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */
-enum class KeyguardQuickAffordancePosition {
- BOTTOM_START,
- BOTTOM_END,
+import android.content.pm.ParceledListSlice;
+
+/**
+ * {@hide}
+ */
+interface IBackgroundInstallControlService {
+ ParceledListSlice getBackgroundInstalledPackages(long flags, int userId);
}
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index b9cef0f..30ee118 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -85,7 +85,7 @@
ICancellationSignal cancelRemote = null;
try {
cancelRemote = mService.executeGetCredential(request,
- new GetCredentialTransport(executor, callback));
+ new GetCredentialTransport(executor, callback), mContext.getOpPackageName());
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
@@ -124,7 +124,8 @@
ICancellationSignal cancelRemote = null;
try {
cancelRemote = mService.executeCreateCredential(request,
- new CreateCredentialTransport(executor, callback));
+ new CreateCredentialTransport(executor, callback),
+ mContext.getOpPackageName());
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index dcf7106..b0f27f9 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -29,7 +29,7 @@
*/
interface ICredentialManager {
- @nullable ICancellationSignal executeGetCredential(in GetCredentialRequest request, in IGetCredentialCallback callback);
+ @nullable ICancellationSignal executeGetCredential(in GetCredentialRequest request, in IGetCredentialCallback callback, String callingPackage);
- @nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback);
+ @nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);
}
diff --git a/core/java/android/credentials/ui/ProviderData.java b/core/java/android/credentials/ui/ProviderData.java
index 35e12fa..3728469 100644
--- a/core/java/android/credentials/ui/ProviderData.java
+++ b/core/java/android/credentials/ui/ProviderData.java
@@ -43,10 +43,10 @@
"android.credentials.ui.extra.PROVIDER_DATA_LIST";
@NonNull
- private final String mProviderId;
+ private final String mProviderFlattenedComponentName;
@NonNull
private final String mProviderDisplayName;
- @NonNull
+ @Nullable
private final Icon mIcon;
@NonNull
private final List<Entry> mCredentialEntries;
@@ -58,11 +58,11 @@
private final @CurrentTimeMillisLong long mLastUsedTimeMillis;
public ProviderData(
- @NonNull String providerId, @NonNull String providerDisplayName,
- @NonNull Icon icon, @NonNull List<Entry> credentialEntries,
+ @NonNull String providerFlattenedComponentName, @NonNull String providerDisplayName,
+ @Nullable Icon icon, @NonNull List<Entry> credentialEntries,
@NonNull List<Entry> actionChips, @Nullable Entry authenticationEntry,
@CurrentTimeMillisLong long lastUsedTimeMillis) {
- mProviderId = providerId;
+ mProviderFlattenedComponentName = providerFlattenedComponentName;
mProviderDisplayName = providerDisplayName;
mIcon = icon;
mCredentialEntries = credentialEntries;
@@ -73,8 +73,8 @@
/** Returns the unique provider id. */
@NonNull
- public String getProviderId() {
- return mProviderId;
+ public String getProviderFlattenedComponentName() {
+ return mProviderFlattenedComponentName;
}
@NonNull
@@ -82,7 +82,7 @@
return mProviderDisplayName;
}
- @NonNull
+ @Nullable
public Icon getIcon() {
return mIcon;
}
@@ -108,9 +108,9 @@
}
protected ProviderData(@NonNull Parcel in) {
- String providerId = in.readString8();
- mProviderId = providerId;
- AnnotationValidations.validate(NonNull.class, null, mProviderId);
+ String providerFlattenedComponentName = in.readString8();
+ mProviderFlattenedComponentName = providerFlattenedComponentName;
+ AnnotationValidations.validate(NonNull.class, null, mProviderFlattenedComponentName);
String providerDisplayName = in.readString8();
mProviderDisplayName = providerDisplayName;
@@ -118,7 +118,6 @@
Icon icon = in.readTypedObject(Icon.CREATOR);
mIcon = icon;
- AnnotationValidations.validate(NonNull.class, null, mIcon);
List<Entry> credentialEntries = new ArrayList<>();
in.readTypedList(credentialEntries, Entry.CREATOR);
@@ -139,7 +138,7 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeString8(mProviderId);
+ dest.writeString8(mProviderFlattenedComponentName);
dest.writeString8(mProviderDisplayName);
dest.writeTypedObject(mIcon, flags);
dest.writeTypedList(mCredentialEntries);
@@ -171,26 +170,27 @@
* @hide
*/
public static class Builder {
- private @NonNull String mProviderId;
+ private @NonNull String mProviderFlattenedComponentName;
private @NonNull String mProviderDisplayName;
- private @NonNull Icon mIcon;
+ private @Nullable Icon mIcon;
private @NonNull List<Entry> mCredentialEntries = new ArrayList<>();
private @NonNull List<Entry> mActionChips = new ArrayList<>();
private @Nullable Entry mAuthenticationEntry = null;
private @CurrentTimeMillisLong long mLastUsedTimeMillis = 0L;
/** Constructor with required properties. */
- public Builder(@NonNull String providerId, @NonNull String providerDisplayName,
- @NonNull Icon icon) {
- mProviderId = providerId;
+ public Builder(@NonNull String providerFlattenedComponentName,
+ @NonNull String providerDisplayName,
+ @Nullable Icon icon) {
+ mProviderFlattenedComponentName = providerFlattenedComponentName;
mProviderDisplayName = providerDisplayName;
mIcon = icon;
}
/** Sets the unique provider id. */
@NonNull
- public Builder setProviderId(@NonNull String providerId) {
- mProviderId = providerId;
+ public Builder setProviderFlattenedComponentName(@NonNull String providerFlattenedComponentName) {
+ mProviderFlattenedComponentName = providerFlattenedComponentName;
return this;
}
@@ -239,7 +239,8 @@
/** Builds a {@link ProviderData}. */
@NonNull
public ProviderData build() {
- return new ProviderData(mProviderId, mProviderDisplayName, mIcon, mCredentialEntries,
+ return new ProviderData(mProviderFlattenedComponentName, mProviderDisplayName,
+ mIcon, mCredentialEntries,
mActionChips, mAuthenticationEntry, mLastUsedTimeMillis);
}
}
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 5403f08..3c73eb6 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -918,6 +918,22 @@
}
}
+ /**
+ * @hide
+ */
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+ public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
+ if (mService == null) {
+ Slog.w(TAG, "setUdfpsOverlay: no fingerprint service");
+ return;
+ }
+
+ try {
+ mService.setUdfpsOverlay(controller);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
/**
* Forwards BiometricStateListener to FingerprintService
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 051e3a4..365a6b3 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -26,6 +26,7 @@
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import java.util.List;
@@ -201,6 +202,10 @@
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
void setSidefpsController(in ISidefpsController controller);
+ // Sets the controller for managing the UDFPS overlay.
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+ void setUdfpsOverlay(in IUdfpsOverlay controller);
+
// Registers BiometricStateListener.
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
void registerBiometricStateListener(IBiometricStateListener listener);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt b/core/java/android/hardware/fingerprint/IUdfpsOverlay.aidl
similarity index 65%
copy from packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
copy to core/java/android/hardware/fingerprint/IUdfpsOverlay.aidl
index 581dafa3..c99fccc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
+++ b/core/java/android/hardware/fingerprint/IUdfpsOverlay.aidl
@@ -14,10 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.domain.model
+package android.hardware.fingerprint;
-/** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */
-enum class KeyguardQuickAffordancePosition {
- BOTTOM_START,
- BOTTOM_END,
+/**
+ * Interface for interacting with the under-display fingerprint sensor (UDFPS) overlay.
+ * @hide
+ */
+oneway interface IUdfpsOverlay {
+ // Shows the overlay.
+ void show(long requestId, int sensorId, int reason);
+
+ // Hides the overlay.
+ void hide(int sensorId);
}
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index f213224b..49c0f92 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -161,4 +161,11 @@
void registerBatteryListener(int deviceId, IInputDeviceBatteryListener listener);
void unregisterBatteryListener(int deviceId, IInputDeviceBatteryListener listener);
+
+ // Get the bluetooth address of an input device if known, returning null if it either is not
+ // connected via bluetooth or if the address cannot be determined.
+ @EnforcePermission("BLUETOOTH")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.BLUETOOTH)")
+ String getInputDeviceBluetoothAddress(int deviceId);
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 8d4aac4..0cf15f7 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1481,6 +1481,24 @@
}
/**
+ * Returns the Bluetooth address of this input device, if known.
+ *
+ * The returned string is always null if this input device is not connected
+ * via Bluetooth, or if the Bluetooth address of the device cannot be
+ * determined. The returned address will look like: "11:22:33:44:55:66".
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @Nullable
+ public String getInputDeviceBluetoothAddress(int deviceId) {
+ try {
+ return mIm.getInputDeviceBluetoothAddress(deviceId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Gets a vibrator service associated with an input device, always creates a new instance.
* @return The vibrator, never null.
* @hide
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index d3a6323..3c4abab 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -562,7 +562,7 @@
*
* @hide
*/
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
public final native void markVintfStability();
/**
@@ -1219,25 +1219,40 @@
@UnsupportedAppUsage
private boolean execTransact(int code, long dataObj, long replyObj,
int flags) {
+
+ Parcel data = Parcel.obtain(dataObj);
+ Parcel reply = Parcel.obtain(replyObj);
+
// At that point, the parcel request headers haven't been parsed so we do not know what
// {@link WorkSource} the caller has set. Use calling UID as the default.
- final int callingUid = Binder.getCallingUid();
- final long origWorkSource = ThreadLocalWorkSource.setUid(callingUid);
+ //
+ // TODO: this is wrong - we should attribute along the entire call route
+ // also this attribution logic should move to native code - it only works
+ // for Java now
+ //
+ // This attribution support is not generic and therefore not support in RPC mode
+ final int callingUid = data.isForRpc() ? -1 : Binder.getCallingUid();
+ final long origWorkSource = callingUid == -1
+ ? -1 : ThreadLocalWorkSource.setUid(callingUid);
+
try {
- return execTransactInternal(code, dataObj, replyObj, flags, callingUid);
+ return execTransactInternal(code, data, reply, flags, callingUid);
} finally {
- ThreadLocalWorkSource.restore(origWorkSource);
+ reply.recycle();
+ data.recycle();
+
+ if (callingUid != -1) {
+ ThreadLocalWorkSource.restore(origWorkSource);
+ }
}
}
- private boolean execTransactInternal(int code, long dataObj, long replyObj, int flags,
+ private boolean execTransactInternal(int code, Parcel data, Parcel reply, int flags,
int callingUid) {
// Make sure the observer won't change while processing a transaction.
final BinderInternal.Observer observer = sObserver;
final CallSession callSession =
observer != null ? observer.callStarted(this, code, UNSET_WORKSOURCE) : null;
- Parcel data = Parcel.obtain(dataObj);
- Parcel reply = Parcel.obtain(replyObj);
// Theoretically, we should call transact, which will call onTransact,
// but all that does is rewind it, and we just got these from an IPC,
// so we'll just call it directly.
@@ -1268,8 +1283,10 @@
final boolean tracingEnabled = tagEnabled && transactionTraceName != null;
try {
+ // TODO - this logic should not be in Java - it should be in native
+ // code in libbinder so that it works for all binder users.
final BinderCallHeavyHitterWatcher heavyHitterWatcher = sHeavyHitterWatcher;
- if (heavyHitterWatcher != null) {
+ if (heavyHitterWatcher != null && callingUid != -1) {
// Notify the heavy hitter watcher, if it's enabled.
heavyHitterWatcher.onTransaction(callingUid, getClass(), code);
}
@@ -1277,7 +1294,10 @@
Trace.traceBegin(Trace.TRACE_TAG_AIDL, transactionTraceName);
}
- if ((flags & FLAG_COLLECT_NOTED_APP_OPS) != 0) {
+ // TODO - this logic should not be in Java - it should be in native
+ // code in libbinder so that it works for all binder users. Further,
+ // this should not re-use flags.
+ if ((flags & FLAG_COLLECT_NOTED_APP_OPS) != 0 && callingUid != -1) {
AppOpsManager.startNotedAppOpsCollection(callingUid);
try {
res = onTransact(code, data, reply, flags);
@@ -1320,8 +1340,6 @@
}
checkParcel(this, code, reply, "Unreasonably large binder reply buffer");
- reply.recycle();
- data.recycle();
}
// Just in case -- we are done with the IPC, so there should be no more strict
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index d451765..2afa879 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -367,6 +367,8 @@
@FastNative
private static native void nativeMarkForBinder(long nativePtr, IBinder binder);
@CriticalNative
+ private static native boolean nativeIsForRpc(long nativePtr);
+ @CriticalNative
private static native int nativeDataSize(long nativePtr);
@CriticalNative
private static native int nativeDataAvail(long nativePtr);
@@ -644,6 +646,15 @@
nativeMarkForBinder(mNativePtr, binder);
}
+ /**
+ * Whether this Parcel is written for an RPC transaction.
+ *
+ * @hide
+ */
+ public final boolean isForRpc() {
+ return nativeIsForRpc(mNativePtr);
+ }
+
/** @hide */
@ParcelFlags
@TestApi
diff --git a/core/java/android/os/PermissionEnforcer.java b/core/java/android/os/PermissionEnforcer.java
new file mode 100644
index 0000000..221e89a
--- /dev/null
+++ b/core/java/android/os/PermissionEnforcer.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+import android.annotation.SystemService;
+import android.content.AttributionSource;
+import android.content.Context;
+import android.content.PermissionChecker;
+import android.permission.PermissionCheckerManager;
+
+/**
+ * PermissionEnforcer check permissions for AIDL-generated services which use
+ * the @EnforcePermission annotation.
+ *
+ * <p>AIDL services may be annotated with @EnforcePermission which will trigger
+ * the generation of permission check code. This generated code relies on
+ * PermissionEnforcer to validate the permissions. The methods available are
+ * purposely similar to the AIDL annotation syntax.
+ *
+ * @see android.permission.PermissionManager
+ *
+ * @hide
+ */
+@SystemService(Context.PERMISSION_ENFORCER_SERVICE)
+public class PermissionEnforcer {
+
+ private final Context mContext;
+
+ /** Protected constructor. Allows subclasses to instantiate an object
+ * without using a Context.
+ */
+ protected PermissionEnforcer() {
+ mContext = null;
+ }
+
+ /** Constructor, prefer using the fromContext static method when possible */
+ public PermissionEnforcer(@NonNull Context context) {
+ mContext = context;
+ }
+
+ @PermissionCheckerManager.PermissionResult
+ protected int checkPermission(@NonNull String permission, @NonNull AttributionSource source) {
+ return PermissionChecker.checkPermissionForDataDelivery(
+ mContext, permission, PermissionChecker.PID_UNKNOWN, source, "" /* message */);
+ }
+
+ public void enforcePermission(@NonNull String permission, @NonNull
+ AttributionSource source) throws SecurityException {
+ int result = checkPermission(permission, source);
+ if (result != PermissionCheckerManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Access denied, requires: " + permission);
+ }
+ }
+
+ public void enforcePermissionAllOf(@NonNull String[] permissions,
+ @NonNull AttributionSource source) throws SecurityException {
+ for (String permission : permissions) {
+ int result = checkPermission(permission, source);
+ if (result != PermissionCheckerManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Access denied, requires: allOf={"
+ + String.join(", ", permissions) + "}");
+ }
+ }
+ }
+
+ public void enforcePermissionAnyOf(@NonNull String[] permissions,
+ @NonNull AttributionSource source) throws SecurityException {
+ for (String permission : permissions) {
+ int result = checkPermission(permission, source);
+ if (result == PermissionCheckerManager.PERMISSION_GRANTED) {
+ return;
+ }
+ }
+ throw new SecurityException("Access denied, requires: anyOf={"
+ + String.join(", ", permissions) + "}");
+ }
+
+ /**
+ * Returns a new PermissionEnforcer based on a Context.
+ *
+ * @hide
+ */
+ public static PermissionEnforcer fromContext(@NonNull Context context) {
+ return context.getSystemService(PermissionEnforcer.class);
+ }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index cd2bbeb..897b7c3 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3374,9 +3374,26 @@
}
}
- // Fetch all flags for the namespace at once for caching purposes
- Bundle b = cp.call(cr.getAttributionSource(),
- mProviderHolder.mUri.getAuthority(), mCallListCommand, null, args);
+ Bundle b;
+ // b/252663068: if we're in system server and the caller did not call
+ // clearCallingIdentity, the read would fail due to mismatched AttributionSources.
+ // TODO(b/256013480): remove this bypass after fixing the callers in system server.
+ if (namespace.equals(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER)
+ && Settings.isInSystemServer()
+ && Binder.getCallingUid() != Process.myUid()) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // Fetch all flags for the namespace at once for caching purposes
+ b = cp.call(cr.getAttributionSource(),
+ mProviderHolder.mUri.getAuthority(), mCallListCommand, null, args);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ } else {
+ // Fetch all flags for the namespace at once for caching purposes
+ b = cp.call(cr.getAttributionSource(),
+ mProviderHolder.mUri.getAuthority(), mCallListCommand, null, args);
+ }
if (b == null) {
// Invalid response, return an empty map
return keyValues;
@@ -7135,7 +7152,7 @@
* Format like "ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0"
* where imeId is ComponentName and subtype is int32.
*/
- @Readable
+ @Readable(maxTargetSdk = Build.VERSION_CODES.TIRAMISU)
public static final String ENABLED_INPUT_METHODS = "enabled_input_methods";
/**
@@ -7144,7 +7161,7 @@
* by ':'.
* @hide
*/
- @Readable
+ @Readable(maxTargetSdk = Build.VERSION_CODES.TIRAMISU)
public static final String DISABLED_SYSTEM_INPUT_METHODS = "disabled_system_input_methods";
/**
diff --git a/core/java/android/service/credentials/CredentialEntry.java b/core/java/android/service/credentials/CredentialEntry.java
index 4cc43a1..a3fa979 100644
--- a/core/java/android/service/credentials/CredentialEntry.java
+++ b/core/java/android/service/credentials/CredentialEntry.java
@@ -140,8 +140,8 @@
public static final class Builder {
private String mType;
private Slice mSlice;
- private PendingIntent mPendingIntent;
- private Credential mCredential;
+ private PendingIntent mPendingIntent = null;
+ private Credential mCredential = null;
private boolean mAutoSelectAllowed = false;
/**
diff --git a/core/java/android/service/credentials/CredentialProviderException.java b/core/java/android/service/credentials/CredentialProviderException.java
index b39b4a0..06f0052 100644
--- a/core/java/android/service/credentials/CredentialProviderException.java
+++ b/core/java/android/service/credentials/CredentialProviderException.java
@@ -30,6 +30,22 @@
public class CredentialProviderException extends Exception {
public static final int ERROR_UNKNOWN = 0;
+ /**
+ * For internal use only.
+ * Error code to be used when the provider request times out.
+ *
+ * @hide
+ */
+ public static final int ERROR_TIMEOUT = 1;
+
+ /**
+ * For internal use only.
+ * Error code to be used when the async task is canceled internally.
+ *
+ * @hide
+ */
+ public static final int ERROR_TASK_CANCELED = 2;
+
private final int mErrorCode;
/**
@@ -37,6 +53,8 @@
*/
@IntDef(prefix = {"ERROR_"}, value = {
ERROR_UNKNOWN,
+ ERROR_TIMEOUT,
+ ERROR_TASK_CANCELED
})
@Retention(RetentionPolicy.SOURCE)
public @interface CredentialProviderError { }
diff --git a/core/java/android/service/credentials/CredentialProviderInfo.java b/core/java/android/service/credentials/CredentialProviderInfo.java
index e3f8cb7..2c7a983 100644
--- a/core/java/android/service/credentials/CredentialProviderInfo.java
+++ b/core/java/android/service/credentials/CredentialProviderInfo.java
@@ -18,16 +18,21 @@
import android.Manifest;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.AppGlobals;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
import android.os.RemoteException;
+import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
@@ -48,19 +53,21 @@
@NonNull
private final List<String> mCapabilities;
- // TODO: Move the two strings below to CredentialProviderService when ready.
- private static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
- private static final String SERVICE_INTERFACE =
- "android.service.credentials.CredentialProviderService";
-
+ @NonNull
+ private final Context mContext;
+ @Nullable
+ private final Drawable mIcon;
+ @Nullable
+ private final CharSequence mLabel;
/**
* Constructs an information instance of the credential provider.
*
- * @param context The context object
- * @param serviceComponent The serviceComponent of the provider service
- * @param userId The android userId for which the current process is running
+ * @param context the context object
+ * @param serviceComponent the serviceComponent of the provider service
+ * @param userId the android userId for which the current process is running
* @throws PackageManager.NameNotFoundException If provider service is not found
+ * @throws SecurityException If provider does not require the relevant permission
*/
public CredentialProviderInfo(@NonNull Context context,
@NonNull ComponentName serviceComponent, int userId)
@@ -68,7 +75,13 @@
this(context, getServiceInfoOrThrow(serviceComponent, userId));
}
- private CredentialProviderInfo(@NonNull Context context, @NonNull ServiceInfo serviceInfo) {
+ /**
+ * Constructs an information instance of the credential provider.
+ * @param context the context object
+ * @param serviceInfo the service info for the provider app. This must be retrieved from the
+ * {@code PackageManager}
+ */
+ public CredentialProviderInfo(@NonNull Context context, @NonNull ServiceInfo serviceInfo) {
if (!Manifest.permission.BIND_CREDENTIAL_PROVIDER_SERVICE.equals(serviceInfo.permission)) {
Log.i(TAG, "Credential Provider Service from : " + serviceInfo.packageName
+ "does not require permission"
@@ -76,32 +89,43 @@
throw new SecurityException("Service does not require the expected permission : "
+ Manifest.permission.BIND_CREDENTIAL_PROVIDER_SERVICE);
}
+ mContext = context;
mServiceInfo = serviceInfo;
mCapabilities = new ArrayList<>();
- populateProviderCapabilities(context);
+ mIcon = mServiceInfo.loadIcon(mContext.getPackageManager());
+ mLabel = mServiceInfo.loadSafeLabel(
+ mContext.getPackageManager(), 0 /* do not ellipsize */,
+ TextUtils.SAFE_STRING_FLAG_FIRST_LINE | TextUtils.SAFE_STRING_FLAG_TRIM);
+ populateProviderCapabilities(context, serviceInfo);
}
- private void populateProviderCapabilities(@NonNull Context context) {
- if (mServiceInfo.applicationInfo.metaData == null) {
- return;
- }
+ private void populateProviderCapabilities(@NonNull Context context, ServiceInfo serviceInfo) {
+ final PackageManager pm = context.getPackageManager();
try {
- final int resourceId = mServiceInfo.applicationInfo.metaData.getInt(
- CAPABILITY_META_DATA_KEY);
- String[] capabilities = context.getResources().getStringArray(resourceId);
- if (capabilities == null) {
- Log.w(TAG, "No capabilities found for provider: " + mServiceInfo.packageName);
+ Bundle metadata = serviceInfo.metaData;
+ Resources resources = pm.getResourcesForApplication(serviceInfo.applicationInfo);
+ if (metadata == null || resources == null) {
+ Log.i(TAG, "populateProviderCapabilities - metadata or resources is null");
return;
}
+
+ String[] capabilities = resources.getStringArray(metadata.getInt(
+ CredentialProviderService.CAPABILITY_META_DATA_KEY));
+ if (capabilities == null || capabilities.length == 0) {
+ Slog.i(TAG, "No capabilities found for provider:" + serviceInfo.packageName);
+ return;
+ }
+
for (String capability : capabilities) {
if (capability.isEmpty()) {
- Log.w(TAG, "Skipping empty capability");
+ Slog.i(TAG, "Skipping empty capability");
continue;
}
+ Slog.i(TAG, "Capabilities found for provider: " + capability);
mCapabilities.add(capability);
}
- } catch (Resources.NotFoundException e) {
- Log.w(TAG, "Exception while populating provider capabilities: " + e.getMessage());
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.i(TAG, e.getMessage());
}
}
@@ -135,6 +159,18 @@
return mServiceInfo;
}
+ /** Returns the service icon. */
+ @Nullable
+ public Drawable getServiceIcon() {
+ return mIcon;
+ }
+
+ /** Returns the service label. */
+ @Nullable
+ public CharSequence getServiceLabel() {
+ return mLabel;
+ }
+
/** Returns an immutable list of capabilities this provider service can support. */
@NonNull
public List<String> getCapabilities() {
@@ -145,14 +181,15 @@
* Returns the valid credential provider services available for the user with the
* given {@code userId}.
*/
+ @NonNull
public static List<CredentialProviderInfo> getAvailableServices(@NonNull Context context,
@UserIdInt int userId) {
final List<CredentialProviderInfo> services = new ArrayList<>();
final List<ResolveInfo> resolveInfos =
context.getPackageManager().queryIntentServicesAsUser(
- new Intent(SERVICE_INTERFACE),
- PackageManager.GET_META_DATA,
+ new Intent(CredentialProviderService.SERVICE_INTERFACE),
+ PackageManager.ResolveInfoFlags.of(PackageManager.GET_META_DATA),
userId);
for (ResolveInfo resolveInfo : resolveInfos) {
final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
@@ -169,8 +206,9 @@
* Returns the valid credential provider services available for the user, that can
* support the given {@code credentialType}.
*/
+ @NonNull
public static List<CredentialProviderInfo> getAvailableServicesForCapability(
- Context context, @UserIdInt int userId, String credentialType) {
+ @NonNull Context context, @UserIdInt int userId, @NonNull String credentialType) {
List<CredentialProviderInfo> servicesForCapability = new ArrayList<>();
final List<CredentialProviderInfo> services = getAvailableServices(context, userId);
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index 1cdf186..b1b08f4 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -42,6 +42,9 @@
*/
public abstract class CredentialProviderService extends Service {
private static final String TAG = "CredProviderService";
+
+ public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
+
private Handler mHandler;
/**
@@ -71,12 +74,13 @@
private final ICredentialProviderService mInterface = new ICredentialProviderService.Stub() {
@Override
- public void onGetCredentials(GetCredentialsRequest request, ICancellationSignal transport,
+ public ICancellationSignal onGetCredentials(GetCredentialsRequest request,
IGetCredentialsCallback callback) {
Objects.requireNonNull(request);
- Objects.requireNonNull(transport);
Objects.requireNonNull(callback);
+ ICancellationSignal transport = CancellationSignal.createTransport();
+
mHandler.sendMessage(obtainMessage(
CredentialProviderService::onGetCredentials,
CredentialProviderService.this, request,
@@ -100,15 +104,17 @@
}
}
));
+ return transport;
}
@Override
- public void onCreateCredential(CreateCredentialRequest request,
- ICancellationSignal transport, ICreateCredentialCallback callback) {
+ public ICancellationSignal onCreateCredential(CreateCredentialRequest request,
+ ICreateCredentialCallback callback) {
Objects.requireNonNull(request);
- Objects.requireNonNull(transport);
Objects.requireNonNull(callback);
+ ICancellationSignal transport = CancellationSignal.createTransport();
+
mHandler.sendMessage(obtainMessage(
CredentialProviderService::onCreateCredential,
CredentialProviderService.this, request,
@@ -132,6 +138,7 @@
}
}
));
+ return transport;
}
};
diff --git a/core/java/android/service/credentials/ICredentialProviderService.aidl b/core/java/android/service/credentials/ICredentialProviderService.aidl
index c68430c..c21cefa 100644
--- a/core/java/android/service/credentials/ICredentialProviderService.aidl
+++ b/core/java/android/service/credentials/ICredentialProviderService.aidl
@@ -21,13 +21,14 @@
import android.service.credentials.CreateCredentialRequest;
import android.service.credentials.IGetCredentialsCallback;
import android.service.credentials.ICreateCredentialCallback;
+import android.os.ICancellationSignal;
/**
* Interface from the system to a credential provider service.
*
* @hide
*/
-oneway interface ICredentialProviderService {
- void onGetCredentials(in GetCredentialsRequest request, in ICancellationSignal transport, in IGetCredentialsCallback callback);
- void onCreateCredential(in CreateCredentialRequest request, in ICancellationSignal transport, in ICreateCredentialCallback callback);
+interface ICredentialProviderService {
+ ICancellationSignal onGetCredentials(in GetCredentialsRequest request, in IGetCredentialsCallback callback);
+ ICancellationSignal onCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback);
}
diff --git a/core/java/android/service/dreams/DreamManagerInternal.java b/core/java/android/service/dreams/DreamManagerInternal.java
index 295171c..5f30ad0 100644
--- a/core/java/android/service/dreams/DreamManagerInternal.java
+++ b/core/java/android/service/dreams/DreamManagerInternal.java
@@ -54,6 +54,13 @@
public abstract void requestDream();
/**
+ * Whether dreaming can start given user settings and the current dock/charge state.
+ *
+ * @param isScreenOn True if the screen is currently on.
+ */
+ public abstract boolean canStartDreaming(boolean isScreenOn);
+
+ /**
* Called by the ActivityTaskManagerService to verify that the startDreamActivity
* request comes from the current active dream component.
*
diff --git a/core/java/android/text/method/BaseKeyListener.java b/core/java/android/text/method/BaseKeyListener.java
index d4bcd12..01989d5 100644
--- a/core/java/android/text/method/BaseKeyListener.java
+++ b/core/java/android/text/method/BaseKeyListener.java
@@ -229,6 +229,8 @@
break;
} else if (Emoji.isEmojiModifierBase(codePoint)) {
deleteCharCount += Character.charCount(codePoint);
+ state = STATE_BEFORE_EMOJI;
+ break;
}
state = STATE_FINISHED;
break;
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index 0a3e6b1..517d982 100755
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -174,6 +174,14 @@
* This is not a density that applications should target, instead relying
* on the system to scale their {@link #DENSITY_XXXHIGH} assets for them.
*/
+ public static final int DENSITY_520 = 520;
+
+ /**
+ * Intermediate density for screens that sit somewhere between
+ * {@link #DENSITY_XXHIGH} (480 dpi) and {@link #DENSITY_XXXHIGH} (640 dpi).
+ * This is not a density that applications should target, instead relying
+ * on the system to scale their {@link #DENSITY_XXXHIGH} assets for them.
+ */
public static final int DENSITY_560 = 560;
/**
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 9b1d867..799955b 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -16,6 +16,7 @@
package android.view;
+import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1010,6 +1011,22 @@
}
/**
+ * Returns the Bluetooth address of this input device, if known.
+ *
+ * The returned string is always null if this input device is not connected
+ * via Bluetooth, or if the Bluetooth address of the device cannot be
+ * determined. The returned address will look like: "11:22:33:44:55:66".
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @Nullable
+ public String getBluetoothAddress() {
+ // We query the address via a separate InputManager API instead of pre-populating it in
+ // this class to avoid leaking it to apps that do not have sufficient permissions.
+ return InputManager.getInstance().getInputDeviceBluetoothAddress(mId);
+ }
+
+ /**
* Gets the vibrator service associated with the device, if there is one.
* Even if the device does not have a vibrator, the result is never null.
* Use {@link Vibrator#hasVibrator} to determine whether a vibrator is
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 5e836ef..ff4588a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -4970,7 +4970,7 @@
}
void reportKeepClearAreasChanged() {
- if (!mHasPendingKeepClearAreaChange) {
+ if (!mHasPendingKeepClearAreaChange || mView == null) {
return;
}
mHasPendingKeepClearAreaChange = false;
diff --git a/core/java/com/android/internal/security/TEST_MAPPING b/core/java/com/android/internal/security/TEST_MAPPING
index 9a5e90e..803760c 100644
--- a/core/java/com/android/internal/security/TEST_MAPPING
+++ b/core/java/com/android/internal/security/TEST_MAPPING
@@ -1,6 +1,17 @@
{
"presubmit": [
{
+ "name": "FrameworksCoreTests",
+ "options": [
+ {
+ "include-filter": "com.android.internal.security."
+ },
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ }
+ ]
+ },
+ {
"name": "ApkVerityTest",
"file_patterns": ["VerityUtils\\.java"]
}
diff --git a/core/java/com/android/internal/security/VerityUtils.java b/core/java/com/android/internal/security/VerityUtils.java
index cb5820f..7f45c09 100644
--- a/core/java/com/android/internal/security/VerityUtils.java
+++ b/core/java/com/android/internal/security/VerityUtils.java
@@ -23,10 +23,28 @@
import android.system.OsConstants;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import com.android.internal.org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import com.android.internal.org.bouncycastle.cms.CMSException;
+import com.android.internal.org.bouncycastle.cms.CMSProcessableByteArray;
+import com.android.internal.org.bouncycastle.cms.CMSSignedData;
+import com.android.internal.org.bouncycastle.cms.SignerInformation;
+import com.android.internal.org.bouncycastle.cms.SignerInformationVerifier;
+import com.android.internal.org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
+import com.android.internal.org.bouncycastle.operator.OperatorCreationException;
+
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
/** Provides fsverity related operations. */
public abstract class VerityUtils {
@@ -91,6 +109,91 @@
}
/**
+ * Verifies the signature over the fs-verity digest using the provided certificate.
+ *
+ * This method should only be used by any existing fs-verity use cases that require
+ * PKCS#7 signature verification, if backward compatibility is necessary.
+ *
+ * Since PKCS#7 is too flexible, for the current specific need, only specific configuration
+ * will be accepted:
+ * <ul>
+ * <li>Must use SHA256 as the digest algorithm
+ * <li>Must use rsaEncryption as signature algorithm
+ * <li>Must be detached / without content
+ * <li>Must not include any signed or unsigned attributes
+ * </ul>
+ *
+ * It is up to the caller to provide an appropriate/trusted certificate.
+ *
+ * @param signatureBlock byte array of a PKCS#7 detached signature
+ * @param digest fs-verity digest with the common configuration using sha256
+ * @param derCertInputStream an input stream of a X.509 certificate in DER
+ * @return whether the verification succeeds
+ */
+ public static boolean verifyPkcs7DetachedSignature(@NonNull byte[] signatureBlock,
+ @NonNull byte[] digest, @NonNull InputStream derCertInputStream) {
+ if (digest.length != 32) {
+ Slog.w(TAG, "Only sha256 is currently supported");
+ return false;
+ }
+
+ try {
+ CMSSignedData signedData = new CMSSignedData(
+ new CMSProcessableByteArray(toFormattedDigest(digest)),
+ signatureBlock);
+
+ if (!signedData.isDetachedSignature()) {
+ Slog.w(TAG, "Expect only detached siganture");
+ return false;
+ }
+ if (!signedData.getCertificates().getMatches(null).isEmpty()) {
+ Slog.w(TAG, "Expect no certificate in signature");
+ return false;
+ }
+ if (!signedData.getCRLs().getMatches(null).isEmpty()) {
+ Slog.w(TAG, "Expect no CRL in signature");
+ return false;
+ }
+
+ X509Certificate trustedCert = (X509Certificate) CertificateFactory.getInstance("X.509")
+ .generateCertificate(derCertInputStream);
+ SignerInformationVerifier verifier = new JcaSimpleSignerInfoVerifierBuilder()
+ .build(trustedCert);
+
+ // Verify any signature with the trusted certificate.
+ for (SignerInformation si : signedData.getSignerInfos().getSigners()) {
+ // To be the most strict while dealing with the complicated PKCS#7 signature, reject
+ // everything we don't need.
+ if (si.getSignedAttributes() != null && si.getSignedAttributes().size() > 0) {
+ Slog.w(TAG, "Unexpected signed attributes");
+ return false;
+ }
+ if (si.getUnsignedAttributes() != null && si.getUnsignedAttributes().size() > 0) {
+ Slog.w(TAG, "Unexpected unsigned attributes");
+ return false;
+ }
+ if (!NISTObjectIdentifiers.id_sha256.getId().equals(si.getDigestAlgOID())) {
+ Slog.w(TAG, "Unsupported digest algorithm OID: " + si.getDigestAlgOID());
+ return false;
+ }
+ if (!PKCSObjectIdentifiers.rsaEncryption.getId().equals(si.getEncryptionAlgOID())) {
+ Slog.w(TAG, "Unsupported encryption algorithm OID: "
+ + si.getEncryptionAlgOID());
+ return false;
+ }
+
+ if (si.verify(verifier)) {
+ return true;
+ }
+ }
+ return false;
+ } catch (CertificateException | CMSException | OperatorCreationException e) {
+ Slog.w(TAG, "Error occurred during the PKCS#7 signature verification", e);
+ }
+ return false;
+ }
+
+ /**
* Returns fs-verity digest for the file if enabled, otherwise returns null. The digest is a
* hash of root hash of fs-verity's Merkle tree with extra metadata.
*
@@ -110,6 +213,19 @@
return result;
}
+ /** @hide */
+ @VisibleForTesting
+ public static byte[] toFormattedDigest(byte[] digest) {
+ // Construct fsverity_formatted_digest used in fs-verity's built-in signature verification.
+ ByteBuffer buffer = ByteBuffer.allocate(12 + digest.length); // struct size + sha256 size
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ buffer.put("FSVerity".getBytes(StandardCharsets.US_ASCII));
+ buffer.putShort((short) 1); // FS_VERITY_HASH_ALG_SHA256
+ buffer.putShort((short) digest.length);
+ buffer.put(digest);
+ return buffer.array();
+ }
+
private static native int enableFsverityNative(@NonNull String filePath,
@NonNull byte[] pkcs7Signature);
private static native int measureFsverityNative(@NonNull String filePath,
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index d59a51a..c50abb3 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -329,7 +329,6 @@
header_libs: [
"bionic_libc_platform_headers",
"dnsproxyd_protocol_headers",
- "libandroid_runtime_vm_headers",
],
},
host: {
@@ -418,24 +417,3 @@
never: true,
},
}
-
-cc_library_headers {
- name: "libandroid_runtime_vm_headers",
- host_supported: true,
- vendor_available: true,
- // TODO(b/153609531): remove when libbinder is not native_bridge_supported
- native_bridge_supported: true,
- // Allow only modules from the following list to create threads that can be
- // attached to the JVM. This list should be a subset of the dependencies of
- // libandroid_runtime.
- visibility: [
- "//frameworks/native/libs/binder",
- ],
- export_include_dirs: ["include_vm"],
- header_libs: [
- "jni_headers",
- ],
- export_header_lib_headers: [
- "jni_headers",
- ],
-}
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 422bdc9..9da28a3 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -22,7 +22,6 @@
#include <android-base/properties.h>
#include <android/graphics/jni_runtime.h>
#include <android_runtime/AndroidRuntime.h>
-#include <android_runtime/vm.h>
#include <assert.h>
#include <binder/IBinder.h>
#include <binder/IPCThreadState.h>
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index 1f64df4..4d8dac1 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -116,6 +116,11 @@
}
}
+static jboolean android_os_Parcel_isForRpc(jlong nativePtr) {
+ Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+ return parcel ? parcel->isForRpc() : false;
+}
+
static jint android_os_Parcel_dataSize(jlong nativePtr)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
@@ -808,6 +813,8 @@
// @FastNative
{"nativeMarkForBinder", "(JLandroid/os/IBinder;)V", (void*)android_os_Parcel_markForBinder},
// @CriticalNative
+ {"nativeIsForRpc", "(J)Z", (void*)android_os_Parcel_isForRpc},
+ // @CriticalNative
{"nativeDataSize", "(J)I", (void*)android_os_Parcel_dataSize},
// @CriticalNative
{"nativeDataAvail", "(J)I", (void*)android_os_Parcel_dataAvail},
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index 39ec037..b2994f4 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -70,6 +70,8 @@
deviceInfo.hasMic(), deviceInfo.hasButtonUnderPad(),
deviceInfo.hasSensor(), deviceInfo.hasBattery(),
deviceInfo.supportsUsi()));
+ // Note: We do not populate the Bluetooth address into the InputDevice object to avoid leaking
+ // it to apps that do not have the Bluetooth permission.
const std::vector<InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges();
for (const InputDeviceInfo::MotionRange& range: ranges) {
diff --git a/core/jni/include_vm/android_runtime/vm.h b/core/jni/include_vm/android_runtime/vm.h
deleted file mode 100644
index a6e7c16..0000000
--- a/core/jni/include_vm/android_runtime/vm.h
+++ /dev/null
@@ -1,24 +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.
- */
-
-#pragma once
-
-#include <jni.h>
-
-// Get the Java VM. If the symbol doesn't exist at runtime, it means libandroid_runtime
-// is not loaded in the current process. If the symbol exists but it returns nullptr, it
-// means JavaVM is not yet started.
-extern "C" JavaVM* AndroidRuntimeGetJavaVM();
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 554b153..62c5848 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6622,6 +6622,15 @@
<permission android:name="android.permission.MANAGE_DEVICE_LOCK_STATE"
android:protectionLevel="internal|role" />
+ <!-- Allows applications to use the long running jobs APIs.
+ <p>This is a special access permission that can be revoked by the system or the user.
+ <p>Apps need to target API {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or above
+ to be able to request this permission.
+ <p>Protection level: appop
+ -->
+ <permission android:name="android.permission.RUN_LONG_JOBS"
+ android:protectionLevel="normal|appop"/>
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
new file mode 100644
index 0000000..fe3ab62
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright (C) 2022 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.hardware.radio.tests.unittests;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.hardware.radio.IRadioService;
+import android.hardware.radio.ITuner;
+import android.hardware.radio.ITunerCallback;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+import android.hardware.radio.RadioMetadata;
+import android.hardware.radio.RadioTuner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class TunerAdapterTest {
+
+ private static final int CALLBACK_TIMEOUT_MS = 30_000;
+ private static final int AM_LOWER_LIMIT_KHZ = 150;
+
+ private static final RadioManager.BandConfig TEST_BAND_CONFIG = createBandConfig();
+
+ private static final ProgramSelector.Identifier FM_IDENTIFIER =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY,
+ /* value= */ 94300);
+ private static final ProgramSelector FM_SELECTOR =
+ new ProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, FM_IDENTIFIER,
+ /* secondaryIds= */ null, /* vendorIds= */ null);
+ private static final RadioManager.ProgramInfo FM_PROGRAM_INFO = createFmProgramInfo();
+
+ private RadioTuner mRadioTuner;
+ private ITunerCallback mTunerCallback;
+
+ @Mock
+ private IRadioService mRadioServiceMock;
+ @Mock
+ private Context mContextMock;
+ @Mock
+ private ITuner mTunerMock;
+ @Mock
+ private RadioTuner.Callback mCallbackMock;
+
+ @Before
+ public void setUp() throws Exception {
+ RadioManager radioManager = new RadioManager(mContextMock, mRadioServiceMock);
+
+ doAnswer(invocation -> {
+ mTunerCallback = (ITunerCallback) invocation.getArguments()[3];
+ return mTunerMock;
+ }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any());
+
+ doAnswer(invocation -> {
+ ProgramSelector program = (ProgramSelector) invocation.getArguments()[0];
+ if (program.getPrimaryId().getType()
+ != ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY) {
+ throw new IllegalArgumentException();
+ }
+ if (program.getPrimaryId().getValue() < AM_LOWER_LIMIT_KHZ) {
+ mTunerCallback.onTuneFailed(RadioManager.STATUS_BAD_VALUE, program);
+ } else {
+ mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO);
+ }
+ return RadioManager.STATUS_OK;
+ }).when(mTunerMock).tune(any());
+
+ mRadioTuner = radioManager.openTuner(/* moduleId= */ 0, TEST_BAND_CONFIG,
+ /* withAudio= */ true, mCallbackMock, /* handler= */ null);
+ }
+
+ @After
+ public void cleanUp() throws Exception {
+ mRadioTuner.close();
+ }
+
+ @Test
+ public void close_forTunerAdapter() throws Exception {
+ mRadioTuner.close();
+
+ verify(mTunerMock).close();
+ }
+
+ @Test
+ public void setConfiguration_forTunerAdapter() throws Exception {
+ int status = mRadioTuner.setConfiguration(TEST_BAND_CONFIG);
+
+ verify(mTunerMock).setConfiguration(TEST_BAND_CONFIG);
+ assertWithMessage("Status for setting configuration")
+ .that(status).isEqualTo(RadioManager.STATUS_OK);
+ }
+
+ @Test
+ public void getConfiguration_forTunerAdapter() throws Exception {
+ when(mTunerMock.getConfiguration()).thenReturn(TEST_BAND_CONFIG);
+ RadioManager.BandConfig[] bandConfigs = new RadioManager.BandConfig[1];
+
+ int status = mRadioTuner.getConfiguration(bandConfigs);
+
+ assertWithMessage("Status for getting configuration")
+ .that(status).isEqualTo(RadioManager.STATUS_OK);
+ assertWithMessage("Configuration obtained from radio tuner")
+ .that(bandConfigs[0]).isEqualTo(TEST_BAND_CONFIG);
+ }
+
+ @Test
+ public void setMute_forTunerAdapter() {
+ int status = mRadioTuner.setMute(/* mute= */ true);
+
+ assertWithMessage("Status for setting mute")
+ .that(status).isEqualTo(RadioManager.STATUS_OK);
+ }
+
+ @Test
+ public void getMute_forTunerAdapter() throws Exception {
+ when(mTunerMock.isMuted()).thenReturn(true);
+
+ boolean muteStatus = mRadioTuner.getMute();
+
+ assertWithMessage("Mute status").that(muteStatus).isTrue();
+ }
+
+ @Test
+ public void step_forTunerAdapter_succeeds() throws Exception {
+ doAnswer(invocation -> {
+ mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO);
+ return RadioManager.STATUS_OK;
+ }).when(mTunerMock).step(anyBoolean(), anyBoolean());
+
+ int scanStatus = mRadioTuner.step(RadioTuner.DIRECTION_DOWN, /* skipSubChannel= */ false);
+
+ verify(mTunerMock).step(/* skipSubChannel= */ true, /* skipSubChannel= */ false);
+ assertWithMessage("Status for stepping")
+ .that(scanStatus).isEqualTo(RadioManager.STATUS_OK);
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO);
+ }
+
+ @Test
+ public void seek_forTunerAdapter_succeeds() throws Exception {
+ doAnswer(invocation -> {
+ mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO);
+ return RadioManager.STATUS_OK;
+ }).when(mTunerMock).scan(anyBoolean(), anyBoolean());
+
+ int scanStatus = mRadioTuner.scan(RadioTuner.DIRECTION_DOWN, /* skipSubChannel= */ false);
+
+ verify(mTunerMock).scan(/* directionDown= */ true, /* skipSubChannel= */ false);
+ assertWithMessage("Status for seeking")
+ .that(scanStatus).isEqualTo(RadioManager.STATUS_OK);
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO);
+ }
+
+ @Test
+ public void seek_forTunerAdapter_invokesOnErrorWhenTimeout() throws Exception {
+ doAnswer(invocation -> {
+ mTunerCallback.onError(RadioTuner.ERROR_SCAN_TIMEOUT);
+ return RadioManager.STATUS_OK;
+ }).when(mTunerMock).scan(anyBoolean(), anyBoolean());
+
+ mRadioTuner.scan(RadioTuner.DIRECTION_UP, /* skipSubChannel*/ true);
+
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onError(RadioTuner.ERROR_SCAN_TIMEOUT);
+ }
+
+ @Test
+ public void tune_withChannelsForTunerAdapter_succeeds() {
+ int status = mRadioTuner.tune(/* channel= */ 92300, /* subChannel= */ 0);
+
+ assertWithMessage("Status for tuning with channel and sub-channel")
+ .that(status).isEqualTo(RadioManager.STATUS_OK);
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO);
+ }
+
+ @Test
+ public void tune_withValidSelectorForTunerAdapter_succeeds() throws Exception {
+ mRadioTuner.tune(FM_SELECTOR);
+
+ verify(mTunerMock).tune(FM_SELECTOR);
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO);
+ }
+
+
+ @Test
+ public void tune_withInvalidSelectorForTunerAdapter_invokesOnTuneFailed() {
+ ProgramSelector invalidSelector = new ProgramSelector(ProgramSelector.PROGRAM_TYPE_FM,
+ new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, /* value= */ 100),
+ /* secondaryIds= */ null, /* vendorIds= */ null);
+
+ mRadioTuner.tune(invalidSelector);
+
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS))
+ .onTuneFailed(RadioManager.STATUS_BAD_VALUE, invalidSelector);
+ }
+
+ @Test
+ public void cancel_forTunerAdapter() throws Exception {
+ mRadioTuner.tune(FM_SELECTOR);
+
+ mRadioTuner.cancel();
+
+ verify(mTunerMock).cancel();
+ }
+
+ @Test
+ public void cancelAnnouncement_forTunerAdapter() throws Exception {
+ mRadioTuner.cancelAnnouncement();
+
+ verify(mTunerMock).cancelAnnouncement();
+ }
+
+ @Test
+ public void getProgramInfo_beforeProgramInfoSetForTunerAdapter() {
+ RadioManager.ProgramInfo[] programInfoArray = new RadioManager.ProgramInfo[1];
+
+ int status = mRadioTuner.getProgramInformation(programInfoArray);
+
+ assertWithMessage("Status for getting null program info")
+ .that(status).isEqualTo(RadioManager.STATUS_INVALID_OPERATION);
+ }
+
+ @Test
+ public void getProgramInfo_afterTuneForTunerAdapter() {
+ mRadioTuner.tune(FM_SELECTOR);
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO);
+ RadioManager.ProgramInfo[] programInfoArray = new RadioManager.ProgramInfo[1];
+
+ int status = mRadioTuner.getProgramInformation(programInfoArray);
+
+ assertWithMessage("Status for getting program info")
+ .that(status).isEqualTo(RadioManager.STATUS_OK);
+ assertWithMessage("Program info obtained from radio tuner")
+ .that(programInfoArray[0]).isEqualTo(FM_PROGRAM_INFO);
+ }
+
+ @Test
+ public void getMetadataImage_forTunerAdapter() throws Exception {
+ Bitmap bitmapExpected = Mockito.mock(Bitmap.class);
+ when(mTunerMock.getImage(anyInt())).thenReturn(bitmapExpected);
+ int imageId = 1;
+
+ Bitmap image = mRadioTuner.getMetadataImage(/* id= */ imageId);
+
+ assertWithMessage("Image obtained from id %s", imageId)
+ .that(image).isEqualTo(bitmapExpected);
+ }
+
+ @Test
+ public void isAnalogForced_forTunerAdapter() throws Exception {
+ when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(true);
+
+ boolean isAnalogForced = mRadioTuner.isAnalogForced();
+
+ assertWithMessage("Forced analog playback switch")
+ .that(isAnalogForced).isTrue();
+ }
+
+ @Test
+ public void setAnalogForced_forTunerAdapter() throws Exception {
+ boolean analogForced = true;
+
+ mRadioTuner.setAnalogForced(analogForced);
+
+ verify(mTunerMock).setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, analogForced);
+ }
+
+ @Test
+ public void isConfigFlagSupported_forTunerAdapter() throws Exception {
+ when(mTunerMock.isConfigFlagSupported(RadioManager.CONFIG_DAB_DAB_LINKING))
+ .thenReturn(true);
+
+ boolean dabFmSoftLinking =
+ mRadioTuner.isConfigFlagSupported(RadioManager.CONFIG_DAB_DAB_LINKING);
+
+ assertWithMessage("Support for DAB-DAB linking config flag")
+ .that(dabFmSoftLinking).isTrue();
+ }
+
+ @Test
+ public void isConfigFlagSet_forTunerAdapter() throws Exception {
+ when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_DAB_FM_SOFT_LINKING))
+ .thenReturn(true);
+
+ boolean dabFmSoftLinking =
+ mRadioTuner.isConfigFlagSet(RadioManager.CONFIG_DAB_FM_SOFT_LINKING);
+
+ assertWithMessage("DAB-FM soft linking config flag")
+ .that(dabFmSoftLinking).isTrue();
+ }
+
+ @Test
+ public void setConfigFlag_forTunerAdapter() throws Exception {
+ boolean dabFmLinking = true;
+
+ mRadioTuner.setConfigFlag(RadioManager.CONFIG_DAB_FM_LINKING, dabFmLinking);
+
+ verify(mTunerMock).setConfigFlag(RadioManager.CONFIG_DAB_FM_LINKING, dabFmLinking);
+ }
+
+ @Test
+ public void getParameters_forTunerAdapter() throws Exception {
+ List<String> parameterKeys = Arrays.asList("ParameterKeyMock");
+ Map<String, String> parameters = Map.of("ParameterKeyMock", "ParameterValueMock");
+ when(mTunerMock.getParameters(parameterKeys)).thenReturn(parameters);
+
+ assertWithMessage("Parameters obtained from radio tuner")
+ .that(mRadioTuner.getParameters(parameterKeys)).isEqualTo(parameters);
+ }
+
+ @Test
+ public void setParameters_forTunerAdapter() throws Exception {
+ Map<String, String> parameters = Map.of("ParameterKeyMock", "ParameterValueMock");
+ when(mTunerMock.setParameters(parameters)).thenReturn(parameters);
+
+ assertWithMessage("Parameters set for radio tuner")
+ .that(mRadioTuner.setParameters(parameters)).isEqualTo(parameters);
+ }
+
+ @Test
+ public void isAntennaConnected_forTunerAdapter() throws Exception {
+ mTunerCallback.onAntennaState(/* connected= */ false);
+
+ assertWithMessage("Antenna connection status")
+ .that(mRadioTuner.isAntennaConnected()).isFalse();
+ }
+
+ @Test
+ public void hasControl_forTunerAdapter() throws Exception {
+ when(mTunerMock.isClosed()).thenReturn(true);
+
+ assertWithMessage("Control on tuner").that(mRadioTuner.hasControl()).isFalse();
+ }
+
+ @Test
+ public void onConfigurationChanged_forTunerCallbackAdapter() throws Exception {
+ mTunerCallback.onConfigurationChanged(TEST_BAND_CONFIG);
+
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS))
+ .onConfigurationChanged(TEST_BAND_CONFIG);
+ }
+
+ @Test
+ public void onTrafficAnnouncement_forTunerCallbackAdapter() throws Exception {
+ mTunerCallback.onTrafficAnnouncement(/* active= */ true);
+
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS))
+ .onTrafficAnnouncement(/* active= */ true);
+ }
+
+ @Test
+ public void onEmergencyAnnouncement_forTunerCallbackAdapter() throws Exception {
+ mTunerCallback.onEmergencyAnnouncement(/* active= */ true);
+
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS))
+ .onEmergencyAnnouncement(/* active= */ true);
+ }
+
+ @Test
+ public void onBackgroundScanAvailabilityChange_forTunerCallbackAdapter() throws Exception {
+ mTunerCallback.onBackgroundScanAvailabilityChange(/* isAvailable= */ false);
+
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS))
+ .onBackgroundScanAvailabilityChange(/* isAvailable= */ false);
+ }
+
+ @Test
+ public void onProgramListChanged_forTunerCallbackAdapter() throws Exception {
+ mTunerCallback.onProgramListChanged();
+
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramListChanged();
+ }
+
+ @Test
+ public void onParametersUpdated_forTunerCallbackAdapter() throws Exception {
+ Map<String, String> parametersExpected = Map.of("ParameterKeyMock", "ParameterValueMock");
+
+ mTunerCallback.onParametersUpdated(parametersExpected);
+
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onParametersUpdated(parametersExpected);
+ }
+
+ private static RadioManager.ProgramInfo createFmProgramInfo() {
+ return new RadioManager.ProgramInfo(FM_SELECTOR, FM_IDENTIFIER, FM_IDENTIFIER,
+ /* relatedContent= */ null, /* infoFlags= */ 0b110001,
+ /* signalQuality= */ 1, createRadioMetadata(), /* vendorInfo= */ null);
+ }
+
+ private static RadioManager.FmBandConfig createBandConfig() {
+ return new RadioManager.FmBandConfig(new RadioManager.FmBandDescriptor(
+ RadioManager.REGION_ITU_1, RadioManager.BAND_FM, /* lowerLimit= */ 87500,
+ /* upperLimit= */ 108000, /* spacing= */ 200, /* stereo= */ true,
+ /* rds= */ false, /* ta= */ false, /* af= */ false, /* es= */ false));
+ }
+
+ private static RadioMetadata createRadioMetadata() {
+ RadioMetadata.Builder metadataBuilder = new RadioMetadata.Builder();
+ return metadataBuilder.putString(RadioMetadata.METADATA_KEY_ARTIST, "artistMock").build();
+ }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
new file mode 100644
index 0000000..3119554
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2022 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.broadcastradio.aidl;
+
+import android.hardware.broadcastradio.AmFmBandRange;
+import android.hardware.broadcastradio.AmFmRegionConfig;
+import android.hardware.broadcastradio.DabTableEntry;
+import android.hardware.broadcastradio.IdentifierType;
+import android.hardware.broadcastradio.Properties;
+import android.hardware.broadcastradio.VendorKeyValue;
+import android.hardware.radio.Announcement;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.Map;
+
+public final class ConversionUtilsTest {
+
+ private static final int FM_LOWER_LIMIT = 87500;
+ private static final int FM_UPPER_LIMIT = 108000;
+ private static final int FM_SPACING = 200;
+ private static final int AM_LOWER_LIMIT = 540;
+ private static final int AM_UPPER_LIMIT = 1700;
+ private static final int AM_SPACING = 10;
+ private static final String DAB_ENTRY_LABEL_1 = "5A";
+ private static final int DAB_ENTRY_FREQUENCY_1 = 174928;
+ private static final String DAB_ENTRY_LABEL_2 = "12D";
+ private static final int DAB_ENTRY_FREQUENCY_2 = 229072;
+ private static final String VENDOR_INFO_KEY_1 = "vendorKey1";
+ private static final String VENDOR_INFO_VALUE_1 = "vendorValue1";
+ private static final String VENDOR_INFO_KEY_2 = "vendorKey2";
+ private static final String VENDOR_INFO_VALUE_2 = "vendorValue2";
+ private static final String TEST_SERVICE_NAME = "serviceMock";
+ private static final int TEST_ID = 1;
+ private static final String TEST_MAKER = "makerMock";
+ private static final String TEST_PRODUCT = "productMock";
+ private static final String TEST_VERSION = "versionMock";
+ private static final String TEST_SERIAL = "serialMock";
+
+ private static final int TEST_ENABLED_TYPE = Announcement.TYPE_EMERGENCY;
+ private static final int TEST_ANNOUNCEMENT_FREQUENCY = FM_LOWER_LIMIT + FM_SPACING;
+
+ private static final RadioManager.ModuleProperties MODULE_PROPERTIES =
+ convertToModuleProperties();
+ private static final Announcement ANNOUNCEMENT =
+ ConversionUtils.announcementFromHalAnnouncement(
+ AidlTestUtils.makeAnnouncement(TEST_ENABLED_TYPE, TEST_ANNOUNCEMENT_FREQUENCY));
+
+ @Rule
+ public final Expect expect = Expect.create();
+
+ @Test
+ public void propertiesFromHalProperties_idsMatch() {
+ expect.withMessage("Properties id")
+ .that(MODULE_PROPERTIES.getId()).isEqualTo(TEST_ID);
+ }
+
+ @Test
+ public void propertiesFromHalProperties_serviceNamesMatch() {
+ expect.withMessage("Service name")
+ .that(MODULE_PROPERTIES.getServiceName()).isEqualTo(TEST_SERVICE_NAME);
+ }
+
+ @Test
+ public void propertiesFromHalProperties_implementorsMatch() {
+ expect.withMessage("Implementor")
+ .that(MODULE_PROPERTIES.getImplementor()).isEqualTo(TEST_MAKER);
+ }
+
+
+ @Test
+ public void propertiesFromHalProperties_productsMatch() {
+ expect.withMessage("Product")
+ .that(MODULE_PROPERTIES.getProduct()).isEqualTo(TEST_PRODUCT);
+ }
+
+ @Test
+ public void propertiesFromHalProperties_versionsMatch() {
+ expect.withMessage("Version")
+ .that(MODULE_PROPERTIES.getVersion()).isEqualTo(TEST_VERSION);
+ }
+
+ @Test
+ public void propertiesFromHalProperties_serialsMatch() {
+ expect.withMessage("Serial")
+ .that(MODULE_PROPERTIES.getSerial()).isEqualTo(TEST_SERIAL);
+ }
+
+ @Test
+ public void propertiesFromHalProperties_dabTableInfoMatch() {
+ Map<String, Integer> dabTableExpected = Map.of(DAB_ENTRY_LABEL_1, DAB_ENTRY_FREQUENCY_1,
+ DAB_ENTRY_LABEL_2, DAB_ENTRY_FREQUENCY_2);
+
+ expect.withMessage("Supported program types")
+ .that(MODULE_PROPERTIES.getDabFrequencyTable())
+ .containsExactlyEntriesIn(dabTableExpected);
+ }
+
+ @Test
+ public void propertiesFromHalProperties_vendorInfoMatch() {
+ Map<String, String> vendorInfoExpected = Map.of(VENDOR_INFO_KEY_1, VENDOR_INFO_VALUE_1,
+ VENDOR_INFO_KEY_2, VENDOR_INFO_VALUE_2);
+
+ expect.withMessage("Vendor info").that(MODULE_PROPERTIES.getVendorInfo())
+ .containsExactlyEntriesIn(vendorInfoExpected);
+ }
+
+ @Test
+ public void propertiesFromHalProperties_bandsMatch() {
+ RadioManager.BandDescriptor[] bands = MODULE_PROPERTIES.getBands();
+
+ expect.withMessage("Band descriptors").that(bands).hasLength(2);
+
+ expect.withMessage("FM band frequency lower limit")
+ .that(bands[0].getLowerLimit()).isEqualTo(FM_LOWER_LIMIT);
+ expect.withMessage("FM band frequency upper limit")
+ .that(bands[0].getUpperLimit()).isEqualTo(FM_UPPER_LIMIT);
+ expect.withMessage("FM band frequency spacing")
+ .that(bands[0].getSpacing()).isEqualTo(FM_SPACING);
+
+ expect.withMessage("AM band frequency lower limit")
+ .that(bands[1].getLowerLimit()).isEqualTo(AM_LOWER_LIMIT);
+ expect.withMessage("AM band frequency upper limit")
+ .that(bands[1].getUpperLimit()).isEqualTo(AM_UPPER_LIMIT);
+ expect.withMessage("AM band frequency spacing")
+ .that(bands[1].getSpacing()).isEqualTo(AM_SPACING);
+ }
+
+ @Test
+ public void announcementFromHalAnnouncement_typesMatch() {
+ expect.withMessage("Announcement type")
+ .that(ANNOUNCEMENT.getType()).isEqualTo(TEST_ENABLED_TYPE);
+ }
+
+ @Test
+ public void announcementFromHalAnnouncement_selectorsMatch() {
+ ProgramSelector.Identifier primaryIdExpected = new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, TEST_ANNOUNCEMENT_FREQUENCY);
+
+ ProgramSelector selector = ANNOUNCEMENT.getSelector();
+
+ expect.withMessage("Primary id of announcement selector")
+ .that(selector.getPrimaryId()).isEqualTo(primaryIdExpected);
+ expect.withMessage("Secondary ids of announcement selector")
+ .that(selector.getSecondaryIds()).isEmpty();
+ }
+
+ @Test
+ public void announcementFromHalAnnouncement_VendorInfoMatch() {
+ expect.withMessage("Announcement vendor info")
+ .that(ANNOUNCEMENT.getVendorInfo()).isEmpty();
+ }
+
+ private static RadioManager.ModuleProperties convertToModuleProperties() {
+ AmFmRegionConfig amFmConfig = createAmFmRegionConfig();
+ DabTableEntry[] dabTableEntries = new DabTableEntry[]{
+ createDabTableEntry(DAB_ENTRY_LABEL_1, DAB_ENTRY_FREQUENCY_1),
+ createDabTableEntry(DAB_ENTRY_LABEL_2, DAB_ENTRY_FREQUENCY_2)};
+ Properties properties = createHalProperties();
+
+ return ConversionUtils.propertiesFromHalProperties(TEST_ID, TEST_SERVICE_NAME, properties,
+ amFmConfig, dabTableEntries);
+ }
+
+ private static AmFmRegionConfig createAmFmRegionConfig() {
+ AmFmRegionConfig amFmRegionConfig = new AmFmRegionConfig();
+ amFmRegionConfig.ranges = new AmFmBandRange[]{
+ createAmFmBandRange(FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING),
+ createAmFmBandRange(AM_LOWER_LIMIT, AM_UPPER_LIMIT, AM_SPACING)};
+ return amFmRegionConfig;
+ }
+
+ private static AmFmBandRange createAmFmBandRange(int lowerBound, int upperBound, int spacing) {
+ AmFmBandRange bandRange = new AmFmBandRange();
+ bandRange.lowerBound = lowerBound;
+ bandRange.upperBound = upperBound;
+ bandRange.spacing = spacing;
+ bandRange.seekSpacing = bandRange.spacing;
+ return bandRange;
+ }
+
+ private static DabTableEntry createDabTableEntry(String label, int value) {
+ DabTableEntry dabTableEntry = new DabTableEntry();
+ dabTableEntry.label = label;
+ dabTableEntry.frequencyKhz = value;
+ return dabTableEntry;
+ }
+
+ private static Properties createHalProperties() {
+ Properties halProperties = new Properties();
+ halProperties.supportedIdentifierTypes = new int[]{IdentifierType.AMFM_FREQUENCY_KHZ,
+ IdentifierType.RDS_PI, IdentifierType.DAB_SID_EXT};
+ halProperties.maker = TEST_MAKER;
+ halProperties.product = TEST_PRODUCT;
+ halProperties.version = TEST_VERSION;
+ halProperties.serial = TEST_SERIAL;
+ halProperties.vendorInfo = new VendorKeyValue[]{
+ AidlTestUtils.makeVendorKeyValue(VENDOR_INFO_KEY_1, VENDOR_INFO_VALUE_1),
+ AidlTestUtils.makeVendorKeyValue(VENDOR_INFO_KEY_2, VENDOR_INFO_VALUE_2)};
+ return halProperties;
+ }
+}
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index a767f83..48cfc87 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -82,6 +82,7 @@
resource_dirs: ["res"],
resource_zips: [":FrameworksCoreTests_apks_as_resources"],
+ java_resources: [":ApkVerityTestCertDer"],
data: [
":BstatsTestApp",
diff --git a/core/tests/coretests/OWNERS b/core/tests/coretests/OWNERS
index 0fb0c30..e8c9fe7 100644
--- a/core/tests/coretests/OWNERS
+++ b/core/tests/coretests/OWNERS
@@ -1 +1,4 @@
include platform/frameworks/base:/services/core/java/com/android/server/am/OWNERS
+
+per-file BinderTest.java = file:platform/frameworks/native:/libs/binder/OWNERS
+per-file ParcelTest.java = file:platform/frameworks/native:/libs/binder/OWNERS
diff --git a/core/tests/coretests/res/raw/fsverity_sig b/core/tests/coretests/res/raw/fsverity_sig
new file mode 100644
index 0000000..b2f335d
--- /dev/null
+++ b/core/tests/coretests/res/raw/fsverity_sig
Binary files differ
diff --git a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
new file mode 100644
index 0000000..67b24ec
--- /dev/null
+++ b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2022 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.app.backup;
+
+import static android.app.backup.BackupRestoreEventLogger.OperationType.BACKUP;
+import static android.app.backup.BackupRestoreEventLogger.OperationType.RESTORE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.fail;
+
+import android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType;
+import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class BackupRestoreEventLoggerTest {
+ private static final int DATA_TYPES_ALLOWED = 15;
+
+ private static final String DATA_TYPE_1 = "data_type_1";
+ private static final String DATA_TYPE_2 = "data_type_2";
+ private static final String ERROR_1 = "error_1";
+ private static final String ERROR_2 = "error_2";
+ private static final String METADATA_1 = "metadata_1";
+ private static final String METADATA_2 = "metadata_2";
+
+ private BackupRestoreEventLogger mLogger;
+ private MessageDigest mHashDigest;
+
+ @Before
+ public void setUp() throws Exception {
+ mHashDigest = MessageDigest.getInstance("SHA-256");
+ }
+
+ @Test
+ public void testBackupLogger_rejectsRestoreLogs() {
+ mLogger = new BackupRestoreEventLogger(BACKUP);
+
+ assertThat(mLogger.logItemsRestored(DATA_TYPE_1, /* count */ 5)).isFalse();
+ assertThat(mLogger.logItemsRestoreFailed(DATA_TYPE_1, /* count */ 5, ERROR_1)).isFalse();
+ assertThat(mLogger.logRestoreMetadata(DATA_TYPE_1, /* metadata */ "metadata")).isFalse();
+ }
+
+ @Test
+ public void testRestoreLogger_rejectsBackupLogs() {
+ mLogger = new BackupRestoreEventLogger(RESTORE);
+
+ assertThat(mLogger.logItemsBackedUp(DATA_TYPE_1, /* count */ 5)).isFalse();
+ assertThat(mLogger.logItemsBackupFailed(DATA_TYPE_1, /* count */ 5, ERROR_1)).isFalse();
+ assertThat(mLogger.logBackupMetaData(DATA_TYPE_1, /* metadata */ "metadata")).isFalse();
+ }
+
+ @Test
+ public void testBackupLogger_onlyAcceptsAllowedNumberOfDataTypes() {
+ mLogger = new BackupRestoreEventLogger(BACKUP);
+
+ for (int i = 0; i < DATA_TYPES_ALLOWED; i++) {
+ String dataType = DATA_TYPE_1 + i;
+ assertThat(mLogger.logItemsBackedUp(dataType, /* count */ 5)).isTrue();
+ assertThat(mLogger.logItemsBackupFailed(dataType, /* count */ 5, /* error */ null))
+ .isTrue();
+ assertThat(mLogger.logBackupMetaData(dataType, METADATA_1)).isTrue();
+ }
+
+ assertThat(mLogger.logItemsBackedUp(DATA_TYPE_2, /* count */ 5)).isFalse();
+ assertThat(mLogger.logItemsBackupFailed(DATA_TYPE_2, /* count */ 5, /* error */ null))
+ .isFalse();
+ assertThat(mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1)).isFalse();
+ assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_2)).isEqualTo(Optional.empty());
+ }
+
+ @Test
+ public void testRestoreLogger_onlyAcceptsAllowedNumberOfDataTypes() {
+ mLogger = new BackupRestoreEventLogger(RESTORE);
+
+ for (int i = 0; i < DATA_TYPES_ALLOWED; i++) {
+ String dataType = DATA_TYPE_1 + i;
+ assertThat(mLogger.logItemsRestored(dataType, /* count */ 5)).isTrue();
+ assertThat(mLogger.logItemsRestoreFailed(dataType, /* count */ 5, /* error */ null))
+ .isTrue();
+ assertThat(mLogger.logRestoreMetadata(dataType, METADATA_1)).isTrue();
+ }
+
+ assertThat(mLogger.logItemsRestored(DATA_TYPE_2, /* count */ 5)).isFalse();
+ assertThat(mLogger.logItemsRestoreFailed(DATA_TYPE_2, /* count */ 5, /* error */ null))
+ .isFalse();
+ assertThat(mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1)).isFalse();
+ assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_2)).isEqualTo(Optional.empty());
+ }
+
+ @Test
+ public void testLogBackupMetadata_repeatedCalls_recordsLatestMetadataHash() {
+ mLogger = new BackupRestoreEventLogger(BACKUP);
+
+ mLogger.logBackupMetaData(DATA_TYPE_1, METADATA_1);
+ mLogger.logBackupMetaData(DATA_TYPE_1, METADATA_2);
+
+ byte[] recordedHash = getResultForDataType(mLogger, DATA_TYPE_1).getMetadataHash();
+ byte[] expectedHash = getMetaDataHash(METADATA_2);
+ assertThat(Arrays.equals(recordedHash, expectedHash)).isTrue();
+ }
+
+ @Test
+ public void testLogRestoreMetadata_repeatedCalls_recordsLatestMetadataHash() {
+ mLogger = new BackupRestoreEventLogger(RESTORE);
+
+ mLogger.logRestoreMetadata(DATA_TYPE_1, METADATA_1);
+ mLogger.logRestoreMetadata(DATA_TYPE_1, METADATA_2);
+
+ byte[] recordedHash = getResultForDataType(mLogger, DATA_TYPE_1).getMetadataHash();
+ byte[] expectedHash = getMetaDataHash(METADATA_2);
+ assertThat(Arrays.equals(recordedHash, expectedHash)).isTrue();
+ }
+
+ @Test
+ public void testLogItemsBackedUp_repeatedCalls_recordsTotalItems() {
+ mLogger = new BackupRestoreEventLogger(BACKUP);
+
+ int firstCount = 10;
+ int secondCount = 5;
+ mLogger.logItemsBackedUp(DATA_TYPE_1, firstCount);
+ mLogger.logItemsBackedUp(DATA_TYPE_1, secondCount);
+
+ int dataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getSuccessCount();
+ assertThat(dataTypeCount).isEqualTo(firstCount + secondCount);
+ }
+
+ @Test
+ public void testLogItemsRestored_repeatedCalls_recordsTotalItems() {
+ mLogger = new BackupRestoreEventLogger(RESTORE);
+
+ int firstCount = 10;
+ int secondCount = 5;
+ mLogger.logItemsRestored(DATA_TYPE_1, firstCount);
+ mLogger.logItemsRestored(DATA_TYPE_1, secondCount);
+
+ int dataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getSuccessCount();
+ assertThat(dataTypeCount).isEqualTo(firstCount + secondCount);
+ }
+
+ @Test
+ public void testLogItemsBackedUp_multipleDataTypes_recordsEachDataType() {
+ mLogger = new BackupRestoreEventLogger(BACKUP);
+
+ int firstCount = 10;
+ int secondCount = 5;
+ mLogger.logItemsBackedUp(DATA_TYPE_1, firstCount);
+ mLogger.logItemsBackedUp(DATA_TYPE_2, secondCount);
+
+ int firstDataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getSuccessCount();
+ int secondDataTypeCount = getResultForDataType(mLogger, DATA_TYPE_2).getSuccessCount();
+ assertThat(firstDataTypeCount).isEqualTo(firstCount);
+ assertThat(secondDataTypeCount).isEqualTo(secondCount);
+ }
+
+ @Test
+ public void testLogItemsRestored_multipleDataTypes_recordsEachDataType() {
+ mLogger = new BackupRestoreEventLogger(RESTORE);
+
+ int firstCount = 10;
+ int secondCount = 5;
+ mLogger.logItemsRestored(DATA_TYPE_1, firstCount);
+ mLogger.logItemsRestored(DATA_TYPE_2, secondCount);
+
+ int firstDataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getSuccessCount();
+ int secondDataTypeCount = getResultForDataType(mLogger, DATA_TYPE_2).getSuccessCount();
+ assertThat(firstDataTypeCount).isEqualTo(firstCount);
+ assertThat(secondDataTypeCount).isEqualTo(secondCount);
+ }
+
+ @Test
+ public void testLogItemsBackupFailed_repeatedCalls_recordsTotalItems() {
+ mLogger = new BackupRestoreEventLogger(BACKUP);
+
+ int firstCount = 10;
+ int secondCount = 5;
+ mLogger.logItemsBackupFailed(DATA_TYPE_1, firstCount, /* error */ null);
+ mLogger.logItemsBackupFailed(DATA_TYPE_1, secondCount, "error");
+
+ int dataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getFailCount();
+ assertThat(dataTypeCount).isEqualTo(firstCount + secondCount);
+ }
+
+ @Test
+ public void testLogItemsRestoreFailed_repeatedCalls_recordsTotalItems() {
+ mLogger = new BackupRestoreEventLogger(RESTORE);
+
+ int firstCount = 10;
+ int secondCount = 5;
+ mLogger.logItemsRestoreFailed(DATA_TYPE_1, firstCount, /* error */ null);
+ mLogger.logItemsRestoreFailed(DATA_TYPE_1, secondCount, "error");
+
+ int dataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getFailCount();
+ assertThat(dataTypeCount).isEqualTo(firstCount + secondCount);
+ }
+
+ @Test
+ public void testLogItemsBackupFailed_multipleErrors_recordsEachError() {
+ mLogger = new BackupRestoreEventLogger(BACKUP);
+
+ int firstCount = 10;
+ int secondCount = 5;
+ mLogger.logItemsBackupFailed(DATA_TYPE_1, firstCount, ERROR_1);
+ mLogger.logItemsBackupFailed(DATA_TYPE_1, secondCount, ERROR_2);
+
+ int firstErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
+ .getErrors().get(ERROR_1);
+ int secondErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
+ .getErrors().get(ERROR_2);
+ assertThat(firstErrorTypeCount).isEqualTo(firstCount);
+ assertThat(secondErrorTypeCount).isEqualTo(secondCount);
+ }
+
+ @Test
+ public void testLogItemsRestoreFailed_multipleErrors_recordsEachError() {
+ mLogger = new BackupRestoreEventLogger(RESTORE);
+
+ int firstCount = 10;
+ int secondCount = 5;
+ mLogger.logItemsRestoreFailed(DATA_TYPE_1, firstCount, ERROR_1);
+ mLogger.logItemsRestoreFailed(DATA_TYPE_1, secondCount, ERROR_2);
+
+ int firstErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
+ .getErrors().get(ERROR_1);
+ int secondErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
+ .getErrors().get(ERROR_2);
+ assertThat(firstErrorTypeCount).isEqualTo(firstCount);
+ assertThat(secondErrorTypeCount).isEqualTo(secondCount);
+ }
+
+ private static DataTypeResult getResultForDataType(BackupRestoreEventLogger logger,
+ @BackupRestoreDataType String dataType) {
+ Optional<DataTypeResult> result = getResultForDataTypeIfPresent(logger, dataType);
+ if (result.isEmpty()) {
+ fail("Failed to find result for data type: " + dataType);
+ }
+ return result.get();
+ }
+
+ private static Optional<DataTypeResult> getResultForDataTypeIfPresent(
+ BackupRestoreEventLogger logger, @BackupRestoreDataType String dataType) {
+ List<DataTypeResult> resultList = logger.getLoggingResults();
+ return resultList.stream().filter(
+ dataTypeResult -> dataTypeResult.getDataType().equals(dataType)).findAny();
+ }
+
+ private byte[] getMetaDataHash(String metaData) {
+ return mHashDigest.digest(metaData.getBytes(StandardCharsets.UTF_8));
+ }
+}
diff --git a/core/tests/coretests/src/android/app/backup/OWNERS b/core/tests/coretests/src/android/app/backup/OWNERS
new file mode 100644
index 0000000..53b6c78
--- /dev/null
+++ b/core/tests/coretests/src/android/app/backup/OWNERS
@@ -0,0 +1 @@
+include /services/backup/OWNERS
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java
index fdd278b..e2fe87b4 100644
--- a/core/tests/coretests/src/android/os/ParcelTest.java
+++ b/core/tests/coretests/src/android/os/ParcelTest.java
@@ -37,6 +37,13 @@
private static final String INTERFACE_TOKEN_2 = "Another IBinder interface token";
@Test
+ public void testIsForRpc() {
+ Parcel p = Parcel.obtain();
+ assertEquals(false, p.isForRpc());
+ p.recycle();
+ }
+
+ @Test
public void testCallingWorkSourceUidAfterWrite() {
Parcel p = Parcel.obtain();
// Method does not throw if replaceCallingWorkSourceUid is called before requests headers
diff --git a/core/tests/coretests/src/android/text/method/BackspaceTest.java b/core/tests/coretests/src/android/text/method/BackspaceTest.java
index ddae652..19c2c61 100644
--- a/core/tests/coretests/src/android/text/method/BackspaceTest.java
+++ b/core/tests/coretests/src/android/text/method/BackspaceTest.java
@@ -193,11 +193,15 @@
backspace(state, 0);
state.assertEquals("|");
- // Emoji modifier can be appended to the first emoji.
+ // Emoji modifier can be appended to each emoji.
state.setByString("U+1F469 U+1F3FB U+200D U+1F4BC |");
backspace(state, 0);
state.assertEquals("|");
+ state.setByString("U+1F468 U+1F3FF U+200D U+2764 U+FE0F U+200D U+1F468 U+1F3FB |");
+ backspace(state, 0);
+ state.assertEquals("|");
+
// End with ZERO WIDTH JOINER
state.setByString("U+1F441 U+200D |");
backspace(state, 0);
diff --git a/core/tests/coretests/src/com/android/internal/security/ContentSignerWrapper.java b/core/tests/coretests/src/com/android/internal/security/ContentSignerWrapper.java
new file mode 100644
index 0000000..0254afe
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/security/ContentSignerWrapper.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.security;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.operator.ContentSigner;
+
+import java.io.OutputStream;
+
+/** A wrapper class of ContentSigner */
+class ContentSignerWrapper implements ContentSigner {
+ private final ContentSigner mSigner;
+
+ ContentSignerWrapper(ContentSigner wrapped) {
+ mSigner = wrapped;
+ }
+
+ @Override
+ public AlgorithmIdentifier getAlgorithmIdentifier() {
+ return mSigner.getAlgorithmIdentifier();
+ }
+
+ @Override
+ public OutputStream getOutputStream() {
+ return mSigner.getOutputStream();
+ }
+
+ @Override
+ public byte[] getSignature() {
+ return mSigner.getSignature();
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java b/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java
new file mode 100644
index 0000000..1513654
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.security;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.coretests.R;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.CMSSignedDataGenerator;
+import org.bouncycastle.cms.SignerInfoGenerator;
+import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HexFormat;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class VerityUtilsTest {
+ private static final byte[] SAMPLE_DIGEST = "12345678901234567890123456789012".getBytes();
+ private static final byte[] FORMATTED_SAMPLE_DIGEST = toFormattedDigest(SAMPLE_DIGEST);
+
+ KeyPair mKeyPair;
+ ContentSigner mContentSigner;
+ X509CertificateHolder mCertificateHolder;
+ byte[] mCertificateDerEncoded;
+
+ @Before
+ public void setUp() throws Exception {
+ mKeyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
+ mContentSigner = newFsverityContentSigner(mKeyPair.getPrivate());
+ mCertificateHolder =
+ newX509CertificateHolder(mContentSigner, mKeyPair.getPublic(), "Someone");
+ mCertificateDerEncoded = mCertificateHolder.getEncoded();
+ }
+
+ @Test
+ public void testOnlyAcceptCorrectDigest() throws Exception {
+ byte[] pkcs7Signature =
+ generatePkcs7Signature(mContentSigner, mCertificateHolder, FORMATTED_SAMPLE_DIGEST);
+
+ byte[] anotherDigest = Arrays.copyOf(SAMPLE_DIGEST, SAMPLE_DIGEST.length);
+ anotherDigest[0] ^= (byte) 1;
+
+ assertTrue(verifySignature(pkcs7Signature, SAMPLE_DIGEST, mCertificateDerEncoded));
+ assertFalse(verifySignature(pkcs7Signature, anotherDigest, mCertificateDerEncoded));
+ }
+
+ @Test
+ public void testDigestWithWrongSize() throws Exception {
+ byte[] pkcs7Signature =
+ generatePkcs7Signature(mContentSigner, mCertificateHolder, FORMATTED_SAMPLE_DIGEST);
+ assertTrue(verifySignature(pkcs7Signature, SAMPLE_DIGEST, mCertificateDerEncoded));
+
+ byte[] digestTooShort = Arrays.copyOfRange(SAMPLE_DIGEST, 0, SAMPLE_DIGEST.length - 1);
+ assertFalse(verifySignature(pkcs7Signature, digestTooShort, mCertificateDerEncoded));
+
+ byte[] digestTooLong = Arrays.copyOfRange(SAMPLE_DIGEST, 0, SAMPLE_DIGEST.length + 1);
+ assertFalse(verifySignature(pkcs7Signature, digestTooLong, mCertificateDerEncoded));
+ }
+
+ @Test
+ public void testOnlyAcceptGoodSignature() throws Exception {
+ byte[] pkcs7Signature =
+ generatePkcs7Signature(mContentSigner, mCertificateHolder, FORMATTED_SAMPLE_DIGEST);
+
+ byte[] anotherDigest = Arrays.copyOf(SAMPLE_DIGEST, SAMPLE_DIGEST.length);
+ anotherDigest[0] ^= (byte) 1;
+ byte[] anotherPkcs7Signature =
+ generatePkcs7Signature(
+ mContentSigner, mCertificateHolder, toFormattedDigest(anotherDigest));
+
+ assertTrue(verifySignature(pkcs7Signature, SAMPLE_DIGEST, mCertificateDerEncoded));
+ assertFalse(verifySignature(anotherPkcs7Signature, SAMPLE_DIGEST, mCertificateDerEncoded));
+ }
+
+ @Test
+ public void testOnlyValidCertCanVerify() throws Exception {
+ byte[] pkcs7Signature =
+ generatePkcs7Signature(mContentSigner, mCertificateHolder, FORMATTED_SAMPLE_DIGEST);
+
+ var wrongKeyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
+ var wrongContentSigner = newFsverityContentSigner(wrongKeyPair.getPrivate());
+ var wrongCertificateHolder =
+ newX509CertificateHolder(wrongContentSigner, wrongKeyPair.getPublic(), "Not Me");
+ byte[] wrongCertificateDerEncoded = wrongCertificateHolder.getEncoded();
+
+ assertFalse(verifySignature(pkcs7Signature, SAMPLE_DIGEST, wrongCertificateDerEncoded));
+ }
+
+ @Test
+ public void testRejectSignatureWithContent() throws Exception {
+ CMSSignedDataGenerator generator =
+ newFsveritySignedDataGenerator(mContentSigner, mCertificateHolder);
+ byte[] pkcs7SignatureNonDetached =
+ generatePkcs7SignatureInternal(
+ generator, FORMATTED_SAMPLE_DIGEST, /* encapsulate */ true);
+
+ assertFalse(
+ verifySignature(pkcs7SignatureNonDetached, SAMPLE_DIGEST, mCertificateDerEncoded));
+ }
+
+ @Test
+ public void testRejectSignatureWithCertificate() throws Exception {
+ CMSSignedDataGenerator generator =
+ newFsveritySignedDataGenerator(mContentSigner, mCertificateHolder);
+ generator.addCertificate(mCertificateHolder);
+ byte[] pkcs7Signature =
+ generatePkcs7SignatureInternal(
+ generator, FORMATTED_SAMPLE_DIGEST, /* encapsulate */ false);
+
+ assertFalse(
+ verifySignature(pkcs7Signature, SAMPLE_DIGEST, mCertificateDerEncoded));
+ }
+
+ @Ignore("No easy way to construct test data")
+ @Test
+ public void testRejectSignatureWithCRL() throws Exception {
+ CMSSignedDataGenerator generator =
+ newFsveritySignedDataGenerator(mContentSigner, mCertificateHolder);
+
+ // The current bouncycastle version does not have an easy way to generate a CRL.
+ // TODO: enable the test once this is doable, e.g. with X509v2CRLBuilder.
+ // generator.addCRL(new X509CRLHolder(CertificateList.getInstance(new DERSequence(...))));
+ byte[] pkcs7Signature =
+ generatePkcs7SignatureInternal(
+ generator, FORMATTED_SAMPLE_DIGEST, /* encapsulate */ false);
+
+ assertFalse(
+ verifySignature(pkcs7Signature, SAMPLE_DIGEST, mCertificateDerEncoded));
+ }
+
+ @Test
+ public void testRejectUnsupportedSignatureAlgorithms() throws Exception {
+ var contentSigner = newFsverityContentSigner(mKeyPair.getPrivate(), "MD5withRSA", null);
+ var certificateHolder =
+ newX509CertificateHolder(contentSigner, mKeyPair.getPublic(), "Someone");
+ byte[] pkcs7Signature =
+ generatePkcs7Signature(contentSigner, certificateHolder, FORMATTED_SAMPLE_DIGEST);
+
+ assertFalse(verifySignature(pkcs7Signature, SAMPLE_DIGEST, certificateHolder.getEncoded()));
+ }
+
+ @Test
+ public void testRejectUnsupportedDigestAlgorithm() throws Exception {
+ CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
+ generator.addSignerInfoGenerator(
+ newSignerInfoGenerator(
+ mContentSigner,
+ mCertificateHolder,
+ OIWObjectIdentifiers.idSHA1,
+ true)); // directSignature
+ byte[] pkcs7Signature =
+ generatePkcs7SignatureInternal(
+ generator, FORMATTED_SAMPLE_DIGEST, /* encapsulate */ false);
+
+ assertFalse(verifySignature(pkcs7Signature, SAMPLE_DIGEST, mCertificateDerEncoded));
+ }
+
+ @Test
+ public void testRejectAnySignerInfoAttributes() throws Exception {
+ var generator = new CMSSignedDataGenerator();
+ generator.addSignerInfoGenerator(
+ newSignerInfoGenerator(
+ mContentSigner,
+ mCertificateHolder,
+ NISTObjectIdentifiers.id_sha256,
+ false)); // directSignature
+ byte[] pkcs7Signature =
+ generatePkcs7SignatureInternal(
+ generator, FORMATTED_SAMPLE_DIGEST, /* encapsulate */ false);
+
+ assertFalse(verifySignature(pkcs7Signature, SAMPLE_DIGEST, mCertificateDerEncoded));
+ }
+
+ @Test
+ public void testSignatureGeneratedExternally() throws Exception {
+ var context = InstrumentationRegistry.getInstrumentation().getContext();
+ byte[] cert = getClass().getClassLoader().getResourceAsStream("ApkVerityTestCert.der")
+ .readAllBytes();
+ // The signature is generated by:
+ // fsverity sign <(echo -n fs-verity) fsverity_sig --key=ApkVerityTestKey.pem \
+ // --cert=ApkVerityTestCert.pem
+ byte[] sig = context.getResources().openRawResource(R.raw.fsverity_sig).readAllBytes();
+ // The fs-verity digest is generated by:
+ // fsverity digest --compact <(echo -n fs-verity)
+ byte[] digest = HexFormat.of().parseHex(
+ "3d248ca542a24fc62d1c43b916eae5016878e2533c88238480b26128a1f1af95");
+
+ assertTrue(verifySignature(sig, digest, cert));
+ }
+
+ private static boolean verifySignature(
+ byte[] pkcs7Signature, byte[] fsverityDigest, byte[] certificateDerEncoded) {
+ return VerityUtils.verifyPkcs7DetachedSignature(
+ pkcs7Signature, fsverityDigest, new ByteArrayInputStream(certificateDerEncoded));
+ }
+
+ private static byte[] toFormattedDigest(byte[] digest) {
+ return VerityUtils.toFormattedDigest(digest);
+ }
+
+ private static byte[] generatePkcs7Signature(
+ ContentSigner contentSigner, X509CertificateHolder certificateHolder, byte[] signedData)
+ throws IOException, CMSException, OperatorCreationException {
+ CMSSignedDataGenerator generator =
+ newFsveritySignedDataGenerator(contentSigner, certificateHolder);
+ return generatePkcs7SignatureInternal(generator, signedData, /* encapsulate */ false);
+ }
+
+ private static byte[] generatePkcs7SignatureInternal(
+ CMSSignedDataGenerator generator, byte[] signedData, boolean encapsulate)
+ throws IOException, CMSException, OperatorCreationException {
+ CMSSignedData cmsSignedData =
+ generator.generate(new CMSProcessableByteArray(signedData), encapsulate);
+ return cmsSignedData.toASN1Structure().getEncoded(ASN1Encoding.DL);
+ }
+
+ private static CMSSignedDataGenerator newFsveritySignedDataGenerator(
+ ContentSigner contentSigner, X509CertificateHolder certificateHolder)
+ throws IOException, CMSException, OperatorCreationException {
+ var generator = new CMSSignedDataGenerator();
+ generator.addSignerInfoGenerator(
+ newSignerInfoGenerator(
+ contentSigner,
+ certificateHolder,
+ NISTObjectIdentifiers.id_sha256,
+ true)); // directSignature
+ return generator;
+ }
+
+ private static SignerInfoGenerator newSignerInfoGenerator(
+ ContentSigner contentSigner,
+ X509CertificateHolder certificateHolder,
+ ASN1ObjectIdentifier digestAlgorithmId,
+ boolean directSignature)
+ throws IOException, CMSException, OperatorCreationException {
+ var provider =
+ new BcDigestCalculatorProvider() {
+ /**
+ * Allow the caller to override the digest algorithm, especially when the
+ * default does not work (i.e. BcDigestCalculatorProvider could return null).
+ *
+ * <p>For example, the current fs-verity signature has to use rsaEncryption for
+ * the signature algorithm, but BcDigestCalculatorProvider will return null,
+ * thus we need a way to override.
+ *
+ * <p>TODO: After bouncycastle 1.70, we can remove this override and just use
+ * {@code JcaSignerInfoGeneratorBuilder#setContentDigest}.
+ */
+ @Override
+ public DigestCalculator get(AlgorithmIdentifier algorithm)
+ throws OperatorCreationException {
+ return super.get(new AlgorithmIdentifier(digestAlgorithmId));
+ }
+ };
+ var builder =
+ new JcaSignerInfoGeneratorBuilder(provider).setDirectSignature(directSignature);
+ return builder.build(contentSigner, certificateHolder);
+ }
+
+ private static ContentSigner newFsverityContentSigner(PrivateKey privateKey)
+ throws OperatorCreationException {
+ // fs-verity expects the signature to have rsaEncryption as the exact algorithm, so
+ // override the default.
+ return newFsverityContentSigner(
+ privateKey, "SHA256withRSA", PKCSObjectIdentifiers.rsaEncryption);
+ }
+
+ private static ContentSigner newFsverityContentSigner(
+ PrivateKey privateKey,
+ String signatureAlgorithm,
+ ASN1ObjectIdentifier signatureAlgorithmIdOverride)
+ throws OperatorCreationException {
+ if (signatureAlgorithmIdOverride != null) {
+ return new ContentSignerWrapper(
+ new JcaContentSignerBuilder(signatureAlgorithm).build(privateKey)) {
+ @Override
+ public AlgorithmIdentifier getAlgorithmIdentifier() {
+ return new AlgorithmIdentifier(signatureAlgorithmIdOverride);
+ }
+ };
+ } else {
+ return new JcaContentSignerBuilder(signatureAlgorithm).build(privateKey);
+ }
+ }
+
+ private static X509CertificateHolder newX509CertificateHolder(
+ ContentSigner contentSigner, PublicKey publicKey, String name) {
+ // Time doesn't really matter, as we only care about the key.
+ Instant now = Instant.now();
+
+ return new X509v3CertificateBuilder(
+ new X500Name("CN=Issuer " + name),
+ /* serial= */ BigInteger.valueOf(now.getEpochSecond()),
+ new Date(now.minus(Duration.ofDays(1)).toEpochMilli()),
+ new Date(now.plus(Duration.ofDays(1)).toEpochMilli()),
+ new X500Name("CN=Subject " + name),
+ SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()))
+ .build(contentSigner);
+ }
+}
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 30c3d50..df5f921 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -23,6 +23,10 @@
TODO(b/238217847): This config is temporary until we refactor the base WMComponent. -->
<bool name="config_registerShellTaskOrganizerOnInit">true</bool>
+ <!-- Determines whether to register the shell transitions on init.
+ TODO(b/238217847): This config is temporary until we refactor the base WMComponent. -->
+ <bool name="config_registerShellTransitionsOnInit">true</bool>
+
<!-- Animation duration for PIP when entering. -->
<integer name="config_pipEnterAnimationDuration">425</integer>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
index 48c5f64..bd2ea9c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
@@ -41,7 +41,6 @@
import android.window.WindowContainerTransaction;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.util.concurrent.Executor;
@@ -122,7 +121,7 @@
/** Until all users are converted, we may have mixed-use (eg. Car). */
private boolean isUsingShellTransitions() {
- return mTaskViewTransitions != null && Transitions.ENABLE_SHELL_TRANSITIONS;
+ return mTaskViewTransitions != null && mTaskViewTransitions.isEnabled();
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
index 83335ac..07d5012 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
@@ -87,6 +87,10 @@
// Note: Don't unregister handler since this is a singleton with lifetime bound to Shell
}
+ boolean isEnabled() {
+ return mTransitions.isRegistered();
+ }
+
/**
* Looks through the pending transitions for one matching `taskView`.
* @param taskView the pending transition should be for this.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 64dbfbb..8b8e192 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -507,6 +507,10 @@
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellAnimationThread ShellExecutor animExecutor) {
+ if (!context.getResources().getBoolean(R.bool.config_registerShellTransitionsOnInit)) {
+ // TODO(b/238217847): Force override shell init if registration is disabled
+ shellInit = new ShellInit(mainExecutor);
+ }
return new Transitions(context, shellInit, shellController, organizer, pool,
displayController, mainExecutor, mainHandler, animExecutor);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index 195ff50..2fafe67 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -48,7 +48,6 @@
try {
int result = Settings.System.getIntForUser(context.getContentResolver(),
Settings.System.DESKTOP_MODE, UserHandle.USER_CURRENT);
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "isDesktopModeEnabled=%s", result);
return result != 0;
} catch (Exception e) {
ProtoLog.e(WM_SHELL_DESKTOP_MODE, "Failed to read DESKTOP_MODE setting %s", e);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index c91d54a..b7749fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -69,22 +69,28 @@
/**
* Mark a task with given [taskId] as active.
+ *
+ * @return `true` if the task was not active
*/
- fun addActiveTask(taskId: Int) {
+ fun addActiveTask(taskId: Int): Boolean {
val added = activeTasks.add(taskId)
if (added) {
activeTasksListeners.onEach { it.onActiveTasksChanged() }
}
+ return added
}
/**
* Remove task with given [taskId] from active tasks.
+ *
+ * @return `true` if the task was active
*/
- fun removeActiveTask(taskId: Int) {
+ fun removeActiveTask(taskId: Int): Boolean {
val removed = activeTasks.remove(taskId)
if (removed) {
activeTasksListeners.onEach { it.onActiveTasksChanged() }
}
+ return removed
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index eaa7158..90b35a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -87,11 +87,13 @@
}
if (DesktopModeStatus.IS_SUPPORTED && taskInfo.isVisible) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "Adding active freeform task: #%d", taskInfo.taskId);
- mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTask(taskInfo.taskId));
- mDesktopModeTaskRepository.ifPresent(
- it -> it.updateVisibleFreeformTasks(taskInfo.taskId, true));
+ mDesktopModeTaskRepository.ifPresent(repository -> {
+ if (repository.addActiveTask(taskInfo.taskId)) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "Adding active freeform task: #%d", taskInfo.taskId);
+ }
+ repository.updateVisibleFreeformTasks(taskInfo.taskId, true);
+ });
}
}
@@ -102,11 +104,13 @@
mTasks.remove(taskInfo.taskId);
if (DesktopModeStatus.IS_SUPPORTED) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "Removing active freeform task: #%d", taskInfo.taskId);
- mDesktopModeTaskRepository.ifPresent(it -> it.removeActiveTask(taskInfo.taskId));
- mDesktopModeTaskRepository.ifPresent(
- it -> it.updateVisibleFreeformTasks(taskInfo.taskId, false));
+ mDesktopModeTaskRepository.ifPresent(repository -> {
+ if (repository.removeActiveTask(taskInfo.taskId)) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "Removing active freeform task: #%d", taskInfo.taskId);
+ }
+ repository.updateVisibleFreeformTasks(taskInfo.taskId, false);
+ });
}
if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -123,13 +127,15 @@
mWindowDecorationViewModel.onTaskInfoChanged(state.mTaskInfo);
if (DesktopModeStatus.IS_SUPPORTED) {
- if (taskInfo.isVisible) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "Adding active freeform task: #%d", taskInfo.taskId);
- mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTask(taskInfo.taskId));
- }
- mDesktopModeTaskRepository.ifPresent(
- it -> it.updateVisibleFreeformTasks(taskInfo.taskId, taskInfo.isVisible));
+ mDesktopModeTaskRepository.ifPresent(repository -> {
+ if (taskInfo.isVisible) {
+ if (repository.addActiveTask(taskInfo.taskId)) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "Adding active freeform task: #%d", taskInfo.taskId);
+ }
+ }
+ repository.updateVisibleFreeformTasks(taskInfo.taskId, taskInfo.isVisible);
+ });
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index b9746e3..cbed4b5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -115,8 +115,8 @@
// coordinates so offset the bounds to 0,0
mTmpDestinationRect.offsetTo(0, 0);
mTmpDestinationRect.inset(insets);
- // Scale by the shortest edge and offset such that the top/left of the scaled inset source
- // rect aligns with the top/left of the destination bounds
+ // Scale to the bounds no smaller than the destination and offset such that the top/left
+ // of the scaled inset source rect aligns with the top/left of the destination bounds
final float scale;
if (isInPipDirection
&& sourceRectHint != null && sourceRectHint.width() < sourceBounds.width()) {
@@ -129,9 +129,8 @@
: (float) destinationBounds.height() / sourceBounds.height();
scale = (1 - fraction) * startScale + fraction * endScale;
} else {
- scale = sourceBounds.width() <= sourceBounds.height()
- ? (float) destinationBounds.width() / sourceBounds.width()
- : (float) destinationBounds.height() / sourceBounds.height();
+ scale = Math.max((float) destinationBounds.width() / sourceBounds.width(),
+ (float) destinationBounds.height() / sourceBounds.height());
}
final float left = destinationBounds.left - insets.left * scale;
final float top = destinationBounds.top - insets.top * scale;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index d7ca791..21a1310 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -108,6 +108,14 @@
private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot,
@NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot) {
+ final TransitSession pendingTransition = getPendingTransition(transition);
+ if (pendingTransition != null && pendingTransition.mCanceled) {
+ // The pending transition was canceled, so skip playing animation.
+ t.apply();
+ onFinish(null /* wct */, null /* wctCB */);
+ return;
+ }
+
// Play some place-holder fade animations
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
@@ -170,9 +178,7 @@
}
boolean isPendingTransition(IBinder transition) {
- return isPendingEnter(transition)
- || isPendingDismiss(transition)
- || isPendingRecent(transition);
+ return getPendingTransition(transition) != null;
}
boolean isPendingEnter(IBinder transition) {
@@ -187,22 +193,38 @@
return mPendingDismiss != null && mPendingDismiss.mTransition == transition;
}
+ @Nullable
+ private TransitSession getPendingTransition(IBinder transition) {
+ if (isPendingEnter(transition)) {
+ return mPendingEnter;
+ } else if (isPendingRecent(transition)) {
+ return mPendingRecent;
+ } else if (isPendingDismiss(transition)) {
+ return mPendingDismiss;
+ }
+
+ return null;
+ }
+
/** Starts a transition to enter split with a remote transition animator. */
IBinder startEnterTransition(
@WindowManager.TransitionType int transitType,
WindowContainerTransaction wct,
@Nullable RemoteTransition remoteTransition,
Transitions.TransitionHandler handler,
- @Nullable TransitionCallback callback) {
+ @Nullable TransitionConsumedCallback consumedCallback,
+ @Nullable TransitionFinishedCallback finishedCallback) {
final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
- setEnterTransition(transition, remoteTransition, callback);
+ setEnterTransition(transition, remoteTransition, consumedCallback, finishedCallback);
return transition;
}
/** Sets a transition to enter split. */
void setEnterTransition(@NonNull IBinder transition,
- @Nullable RemoteTransition remoteTransition, @Nullable TransitionCallback callback) {
- mPendingEnter = new TransitSession(transition, callback);
+ @Nullable RemoteTransition remoteTransition,
+ @Nullable TransitionConsumedCallback consumedCallback,
+ @Nullable TransitionFinishedCallback finishedCallback) {
+ mPendingEnter = new TransitSession(transition, consumedCallback, finishedCallback);
if (remoteTransition != null) {
// Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
@@ -237,8 +259,9 @@
}
void setRecentTransition(@NonNull IBinder transition,
- @Nullable RemoteTransition remoteTransition, @Nullable TransitionCallback callback) {
- mPendingRecent = new TransitSession(transition, callback);
+ @Nullable RemoteTransition remoteTransition,
+ @Nullable TransitionFinishedCallback finishCallback) {
+ mPendingRecent = new TransitSession(transition, null /* consumedCb */, finishCallback);
if (remoteTransition != null) {
// Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
@@ -248,7 +271,7 @@
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
- + " deduced Enter recent panel");
+ + " deduced Enter recent panel");
}
void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
@@ -256,14 +279,9 @@
if (mergeTarget != mAnimatingTransition) return;
if (isPendingEnter(transition) && isPendingRecent(mergeTarget)) {
- mPendingRecent.mCallback = new TransitionCallback() {
- @Override
- public void onTransitionFinished(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {
- // Since there's an entering transition merged, recent transition no longer
- // need to handle entering split screen after the transition finished.
- }
- };
+ // Since there's an entering transition merged, recent transition no longer
+ // need to handle entering split screen after the transition finished.
+ mPendingRecent.setFinishedCallback(null);
}
if (mActiveRemoteHandler != null) {
@@ -277,7 +295,7 @@
}
boolean end() {
- // If its remote, there's nothing we can do right now.
+ // If It's remote, there's nothing we can do right now.
if (mActiveRemoteHandler != null) return false;
for (int i = mAnimations.size() - 1; i >= 0; --i) {
final Animator anim = mAnimations.get(i);
@@ -290,20 +308,20 @@
@Nullable SurfaceControl.Transaction finishT) {
if (isPendingEnter(transition)) {
if (!aborted) {
- // An enter transition got merged, appends the rest operations to finish entering
+ // An entering transition got merged, appends the rest operations to finish entering
// split screen.
mStageCoordinator.finishEnterSplitScreen(finishT);
mPendingRemoteHandler = null;
}
- mPendingEnter.mCallback.onTransitionConsumed(aborted);
+ mPendingEnter.onConsumed(aborted);
mPendingEnter = null;
mPendingRemoteHandler = null;
} else if (isPendingDismiss(transition)) {
- mPendingDismiss.mCallback.onTransitionConsumed(aborted);
+ mPendingDismiss.onConsumed(aborted);
mPendingDismiss = null;
} else if (isPendingRecent(transition)) {
- mPendingRecent.mCallback.onTransitionConsumed(aborted);
+ mPendingRecent.onConsumed(aborted);
mPendingRecent = null;
mPendingRemoteHandler = null;
}
@@ -312,23 +330,16 @@
void onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB) {
if (!mAnimations.isEmpty()) return;
- TransitionCallback callback = null;
+ if (wct == null) wct = new WindowContainerTransaction();
if (isPendingEnter(mAnimatingTransition)) {
- callback = mPendingEnter.mCallback;
+ mPendingEnter.onFinished(wct, mFinishTransaction);
mPendingEnter = null;
- }
- if (isPendingDismiss(mAnimatingTransition)) {
- callback = mPendingDismiss.mCallback;
- mPendingDismiss = null;
- }
- if (isPendingRecent(mAnimatingTransition)) {
- callback = mPendingRecent.mCallback;
+ } else if (isPendingRecent(mAnimatingTransition)) {
+ mPendingRecent.onFinished(wct, mFinishTransaction);
mPendingRecent = null;
- }
-
- if (callback != null) {
- if (wct == null) wct = new WindowContainerTransaction();
- callback.onTransitionFinished(wct, mFinishTransaction);
+ } else if (isPendingDismiss(mAnimatingTransition)) {
+ mPendingDismiss.onFinished(wct, mFinishTransaction);
+ mPendingDismiss = null;
}
mPendingRemoteHandler = null;
@@ -363,10 +374,7 @@
onFinish(null /* wct */, null /* wctCB */);
});
};
- va.addListener(new Animator.AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) { }
-
+ va.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
finisher.run();
@@ -376,9 +384,6 @@
public void onAnimationCancel(Animator animation) {
finisher.run();
}
-
- @Override
- public void onAnimationRepeat(Animator animation) { }
});
mAnimations.add(va);
mTransitions.getAnimExecutor().execute(va::start);
@@ -432,24 +437,66 @@
|| info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
}
- /** Clean-up callbacks for transition. */
- interface TransitionCallback {
- /** Calls when the transition got consumed. */
- default void onTransitionConsumed(boolean aborted) {}
+ /** Calls when the transition got consumed. */
+ interface TransitionConsumedCallback {
+ void onConsumed(boolean aborted);
+ }
- /** Calls when the transition finished. */
- default void onTransitionFinished(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {}
+ /** Calls when the transition finished. */
+ interface TransitionFinishedCallback {
+ void onFinished(WindowContainerTransaction wct, SurfaceControl.Transaction t);
}
/** Session for a transition and its clean-up callback. */
static class TransitSession {
final IBinder mTransition;
- TransitionCallback mCallback;
+ TransitionConsumedCallback mConsumedCallback;
+ TransitionFinishedCallback mFinishedCallback;
- TransitSession(IBinder transition, @Nullable TransitionCallback callback) {
+ /** Whether the transition was canceled. */
+ boolean mCanceled;
+
+ TransitSession(IBinder transition,
+ @Nullable TransitionConsumedCallback consumedCallback,
+ @Nullable TransitionFinishedCallback finishedCallback) {
mTransition = transition;
- mCallback = callback != null ? callback : new TransitionCallback() {};
+ mConsumedCallback = consumedCallback;
+ mFinishedCallback = finishedCallback;
+
+ }
+
+ /** Sets transition consumed callback. */
+ void setConsumedCallback(@Nullable TransitionConsumedCallback callback) {
+ mConsumedCallback = callback;
+ }
+
+ /** Sets transition finished callback. */
+ void setFinishedCallback(@Nullable TransitionFinishedCallback callback) {
+ mFinishedCallback = callback;
+ }
+
+ /**
+ * Cancels the transition. This should be called before playing animation. A canceled
+ * transition will skip playing animation.
+ *
+ * @param finishedCb new finish callback to override.
+ */
+ void cancel(@Nullable TransitionFinishedCallback finishedCb) {
+ mCanceled = true;
+ setFinishedCallback(finishedCb);
+ }
+
+ void onConsumed(boolean aborted) {
+ if (mConsumedCallback != null) {
+ mConsumedCallback.onConsumed(aborted);
+ }
+ }
+
+ void onFinished(WindowContainerTransaction finishWct,
+ SurfaceControl.Transaction finishT) {
+ if (mFinishedCallback != null) {
+ mFinishedCallback.onFinished(finishWct, finishT);
+ }
}
}
@@ -459,7 +506,7 @@
final @SplitScreen.StageType int mDismissTop;
DismissTransition(IBinder transition, int reason, int dismissTop) {
- super(transition, null /* callback */);
+ super(transition, null /* consumedCallback */, null /* finishedCallback */);
this.mReason = reason;
this.mDismissTop = dismissTop;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index e2ac01f..943419b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -226,33 +226,36 @@
}
};
- private final SplitScreenTransitions.TransitionCallback mRecentTransitionCallback =
- new SplitScreenTransitions.TransitionCallback() {
- @Override
- public void onTransitionFinished(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {
- // Check if the recent transition is finished by returning to the current split, so we
- // can restore the divider bar.
- for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) {
- final WindowContainerTransaction.HierarchyOp op =
- finishWct.getHierarchyOps().get(i);
- final IBinder container = op.getContainer();
- if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
- && (mMainStage.containsContainer(container)
- || mSideStage.containsContainer(container))) {
- updateSurfaceBounds(mSplitLayout, finishT, false /* applyResizingOffset */);
- setDividerVisibility(true, finishT);
- return;
- }
- }
+ private final SplitScreenTransitions.TransitionFinishedCallback
+ mRecentTransitionFinishedCallback =
+ new SplitScreenTransitions.TransitionFinishedCallback() {
+ @Override
+ public void onFinished(WindowContainerTransaction finishWct,
+ SurfaceControl.Transaction finishT) {
+ // Check if the recent transition is finished by returning to the current
+ // split, so we
+ // can restore the divider bar.
+ for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) {
+ final WindowContainerTransaction.HierarchyOp op =
+ finishWct.getHierarchyOps().get(i);
+ final IBinder container = op.getContainer();
+ if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
+ && (mMainStage.containsContainer(container)
+ || mSideStage.containsContainer(container))) {
+ updateSurfaceBounds(mSplitLayout, finishT,
+ false /* applyResizingOffset */);
+ setDividerVisibility(true, finishT);
+ return;
+ }
+ }
- // Dismiss the split screen if it's not returning to split.
- prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct);
- setSplitsVisible(false);
- setDividerVisibility(false, finishT);
- logExit(EXIT_REASON_UNKNOWN);
- }
- };
+ // Dismiss the split screen if it's not returning to split.
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct);
+ setSplitsVisible(false);
+ setDividerVisibility(false, finishT);
+ logExit(EXIT_REASON_UNKNOWN);
+ }
+ };
protected StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
ShellTaskOrganizer taskOrganizer, DisplayController displayController,
@@ -389,15 +392,11 @@
if (ENABLE_SHELL_TRANSITIONS) {
prepareEnterSplitScreen(wct);
mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct,
- null, this, new SplitScreenTransitions.TransitionCallback() {
- @Override
- public void onTransitionFinished(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {
- if (!evictWct.isEmpty()) {
- finishWct.merge(evictWct, true);
- }
+ null, this, null /* consumedCallback */, (finishWct, finishT) -> {
+ if (!evictWct.isEmpty()) {
+ finishWct.merge(evictWct, true);
}
- });
+ } /* finishedCallback */);
} else {
if (!evictWct.isEmpty()) {
wct.merge(evictWct, true /* transfer */);
@@ -434,28 +433,25 @@
options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
wct.sendPendingIntent(intent, fillInIntent, options);
+
+ // If split screen is not activated, we're expecting to open a pair of apps to split.
+ final int transitType = mMainStage.isActive()
+ ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
prepareEnterSplitScreen(wct, null /* taskInfo */, position);
- mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, null, this,
- new SplitScreenTransitions.TransitionCallback() {
- @Override
- public void onTransitionConsumed(boolean aborted) {
- // Switch the split position if launching as MULTIPLE_TASK failed.
- if (aborted
- && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
- setSideStagePositionAnimated(
- SplitLayout.reversePosition(mSideStagePosition));
- }
+ mSplitTransitions.startEnterTransition(transitType, wct, null, this,
+ aborted -> {
+ // Switch the split position if launching as MULTIPLE_TASK failed.
+ if (aborted && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
+ setSideStagePositionAnimated(
+ SplitLayout.reversePosition(mSideStagePosition));
}
-
- @Override
- public void onTransitionFinished(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {
- if (!evictWct.isEmpty()) {
- finishWct.merge(evictWct, true);
- }
+ } /* consumedCallback */,
+ (finishWct, finishT) -> {
+ if (!evictWct.isEmpty()) {
+ finishWct.merge(evictWct, true);
}
- });
+ } /* finishedCallback */);
}
/** Launches an activity into split by legacy transition. */
@@ -564,9 +560,9 @@
/**
* Starts with the second task to a split pair in one transition.
*
- * @param wct transaction to start the first task
+ * @param wct transaction to start the first task
* @param instanceId if {@code null}, will not log. Otherwise it will be used in
- * {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
+ * {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
*/
private void startWithTask(WindowContainerTransaction wct, int mainTaskId,
@Nullable Bundle mainOptions, float splitRatio,
@@ -592,7 +588,7 @@
wct.startTask(mainTaskId, mainOptions);
mSplitTransitions.startEnterTransition(
- TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null);
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null, null);
setEnterInstanceId(instanceId);
}
@@ -639,7 +635,7 @@
}
/**
- * @param wct transaction to start the first task
+ * @param wct transaction to start the first task
* @param instanceId if {@code null}, will not log. Otherwise it will be used in
* {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
*/
@@ -1082,15 +1078,15 @@
switch (exitReason) {
// One of the apps doesn't support MW
case EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW:
- // User has explicitly dragged the divider to dismiss split
+ // User has explicitly dragged the divider to dismiss split
case EXIT_REASON_DRAG_DIVIDER:
- // Either of the split apps have finished
+ // Either of the split apps have finished
case EXIT_REASON_APP_FINISHED:
- // One of the children enters PiP
+ // One of the children enters PiP
case EXIT_REASON_CHILD_TASK_ENTER_PIP:
- // One of the apps occludes lock screen.
+ // One of the apps occludes lock screen.
case EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP:
- // User has unlocked the device after folded
+ // User has unlocked the device after folded
case EXIT_REASON_DEVICE_FOLDED:
return true;
default:
@@ -1839,7 +1835,7 @@
|| activityType == ACTIVITY_TYPE_RECENTS) {
// Enter overview panel, so start recent transition.
mSplitTransitions.setRecentTransition(transition, request.getRemoteTransition(),
- mRecentTransitionCallback);
+ mRecentTransitionFinishedCallback);
} else if (mSplitTransitions.mPendingRecent == null) {
// If split-task is not controlled by recents animation
// and occluded by the other fullscreen task, dismiss both.
@@ -1853,8 +1849,8 @@
// One task is appearing into split, prepare to enter split screen.
out = new WindowContainerTransaction();
prepareEnterSplitScreen(out);
- mSplitTransitions.setEnterTransition(
- transition, request.getRemoteTransition(), null /* callback */);
+ mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
+ null /* consumedCallback */, null /* finishedCallback */);
}
}
return out;
@@ -1873,7 +1869,7 @@
}
final @WindowManager.TransitionType int type = request.getType();
if (isSplitActive() && !isOpeningType(type)
- && (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0)) {
+ && (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0)) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " One of the splits became "
+ "empty during a mixed transition (one not handled by split),"
+ " so make sure split-screen state is cleaned-up. "
@@ -2031,17 +2027,21 @@
}
}
- // TODO(b/250853925): fallback logic. Probably start a new transition to exit split before
- // applying anything here. Ideally consolidate with transition-merging.
if (info.getType() == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) {
if (mainChild == null && sideChild == null) {
- throw new IllegalStateException("Launched a task in split, but didn't receive any"
- + " task in transition.");
+ Log.w(TAG, "Launched a task in split, but didn't receive any task in transition.");
+ mSplitTransitions.mPendingEnter.cancel(null /* finishedCb */);
+ return true;
}
} else {
if (mainChild == null || sideChild == null) {
- throw new IllegalStateException("Launched 2 tasks in split, but didn't receive"
+ Log.w(TAG, "Launched 2 tasks in split, but didn't receive"
+ " 2 tasks in transition. Possibly one of them failed to launch");
+ final int dismissTop = mainChild != null ? STAGE_TYPE_MAIN :
+ (sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED);
+ mSplitTransitions.mPendingEnter.cancel(
+ (cancelWct, cancelT) -> prepareExitSplitScreen(dismissTop, cancelWct));
+ return true;
}
}
@@ -2305,7 +2305,7 @@
final int stageType = isMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
final WindowContainerTransaction wct = new WindowContainerTransaction();
prepareExitSplitScreen(stageType, wct);
- mSplitTransitions.startDismissTransition(wct,StageCoordinator.this, stageType,
+ mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, stageType,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
mSplitUnsupportedToast.show();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 394d6f6..63d31cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -122,6 +122,8 @@
private final ShellController mShellController;
private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
+ private boolean mIsRegistered = false;
+
/** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();
@@ -163,19 +165,18 @@
displayController, pool, mainExecutor, mainHandler, animExecutor);
mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor);
mShellController = shellController;
- shellInit.addInitCallback(this::onInit, this);
- }
-
- private void onInit() {
- mShellController.addExternalInterface(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
- this::createExternalInterface, this);
-
// The very last handler (0 in the list) should be the default one.
mHandlers.add(mDefaultTransitionHandler);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Default");
// Next lowest priority is remote transitions.
mHandlers.add(mRemoteTransitionHandler);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
+ this::createExternalInterface, this);
ContentResolver resolver = mContext.getContentResolver();
mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
@@ -186,13 +187,23 @@
new SettingsObserver());
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mIsRegistered = true;
// Register this transition handler with Core
- mOrganizer.registerTransitionPlayer(mPlayerImpl);
+ try {
+ mOrganizer.registerTransitionPlayer(mPlayerImpl);
+ } catch (RuntimeException e) {
+ mIsRegistered = false;
+ throw e;
+ }
// Pre-load the instance.
TransitionMetrics.getInstance();
}
}
+ public boolean isRegistered() {
+ return mIsRegistered;
+ }
+
private float getTransitionAnimationScaleSetting() {
return fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
Settings.Global.TRANSITION_ANIMATION_SCALE, mContext.getResources().getFloat(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index ea0033b..652f9b3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -181,7 +181,7 @@
IBinder transition = mSplitScreenTransitions.startEnterTransition(
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(),
- new RemoteTransition(testRemote), mStageCoordinator, null);
+ new RemoteTransition(testRemote), mStageCoordinator, null, null);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
boolean accepted = mStageCoordinator.startAnimation(transition, info,
@@ -421,7 +421,7 @@
TransitionInfo enterInfo = createEnterPairInfo();
IBinder enterTransit = mSplitScreenTransitions.startEnterTransition(
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(),
- new RemoteTransition(new TestRemoteTransition()), mStageCoordinator, null);
+ new RemoteTransition(new TestRemoteTransition()), mStageCoordinator, null, null);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
mStageCoordinator.startAnimation(enterTransit, enterInfo,
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 6a0c5a8..d09bc47 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -472,11 +472,11 @@
mRenderThread.pushBackFrameCallback(this);
}
-nsecs_t CanvasContext::draw() {
+std::optional<nsecs_t> CanvasContext::draw() {
if (auto grContext = getGrContext()) {
if (grContext->abandoned()) {
LOG_ALWAYS_FATAL("GrContext is abandoned/device lost at start of CanvasContext::draw");
- return 0;
+ return std::nullopt;
}
}
SkRect dirty;
@@ -498,7 +498,7 @@
std::invoke(func, false /* didProduceBuffer */);
}
mFrameCommitCallbacks.clear();
- return 0;
+ return std::nullopt;
}
ScopedActiveContext activeContext(this);
@@ -543,6 +543,8 @@
}
bool requireSwap = false;
+ bool didDraw = false;
+
int error = OK;
bool didSwap = mRenderPipeline->swapBuffers(frame, drawResult.success, windowDirty,
mCurrentFrameInfo, &requireSwap);
@@ -553,7 +555,7 @@
mIsDirty = false;
if (requireSwap) {
- bool didDraw = true;
+ didDraw = true;
// Handle any swapchain errors
error = mNativeSurface->getAndClearError();
if (error == TIMED_OUT) {
@@ -649,7 +651,9 @@
}
mRenderThread.cacheManager().onFrameCompleted();
- return mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration);
+ return didDraw ? std::make_optional(
+ mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration))
+ : std::nullopt;
}
void CanvasContext::reportMetricsWithPresentTime() {
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 748ab96..db96cfb 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -138,7 +138,7 @@
bool makeCurrent();
void prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued, RenderNode* target);
// Returns the DequeueBufferDuration.
- nsecs_t draw();
+ std::optional<nsecs_t> draw();
void destroy();
// IFrameCallback, Choreographer-driven frame callback entry point
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 03f02de..dc7676c 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -19,6 +19,7 @@
#include <dlfcn.h>
#include <gui/TraceUtils.h>
#include <utils/Log.h>
+
#include <algorithm>
#include "../DeferredLayerUpdater.h"
@@ -28,6 +29,7 @@
#include "CanvasContext.h"
#include "RenderThread.h"
#include "thread/CommonPool.h"
+#include "utils/TimeUtils.h"
namespace android {
namespace uirenderer {
@@ -146,6 +148,7 @@
bool canUnblockUiThread;
bool canDrawThisFrame;
+ bool didDraw = false;
{
TreeInfo info(TreeInfo::MODE_FULL, *mContext);
info.forceDrawFrame = mForceDrawFrame;
@@ -188,7 +191,9 @@
nsecs_t dequeueBufferDuration = 0;
if (CC_LIKELY(canDrawThisFrame)) {
- dequeueBufferDuration = context->draw();
+ std::optional<nsecs_t> drawResult = context->draw();
+ didDraw = drawResult.has_value();
+ dequeueBufferDuration = drawResult.value_or(0);
} else {
// Do a flush in case syncFrameState performed any texture uploads. Since we skipped
// the draw() call, those uploads (or deletes) will end up sitting in the queue.
@@ -209,8 +214,9 @@
}
if (!mHintSessionWrapper) mHintSessionWrapper.emplace(mUiThreadId, mRenderThreadId);
- constexpr int64_t kSanityCheckLowerBound = 100000; // 0.1ms
- constexpr int64_t kSanityCheckUpperBound = 10000000000; // 10s
+
+ constexpr int64_t kSanityCheckLowerBound = 100_us;
+ constexpr int64_t kSanityCheckUpperBound = 10_s;
int64_t targetWorkDuration = frameDeadline - intendedVsync;
targetWorkDuration = targetWorkDuration * Properties::targetCpuTimePercentage / 100;
if (targetWorkDuration > kSanityCheckLowerBound &&
@@ -219,12 +225,15 @@
mLastTargetWorkDuration = targetWorkDuration;
mHintSessionWrapper->updateTargetWorkDuration(targetWorkDuration);
}
- int64_t frameDuration = systemTime(SYSTEM_TIME_MONOTONIC) - frameStartTime;
- int64_t actualDuration = frameDuration -
- (std::min(syncDelayDuration, mLastDequeueBufferDuration)) -
- dequeueBufferDuration;
- if (actualDuration > kSanityCheckLowerBound && actualDuration < kSanityCheckUpperBound) {
- mHintSessionWrapper->reportActualWorkDuration(actualDuration);
+
+ if (didDraw) {
+ int64_t frameDuration = systemTime(SYSTEM_TIME_MONOTONIC) - frameStartTime;
+ int64_t actualDuration = frameDuration -
+ (std::min(syncDelayDuration, mLastDequeueBufferDuration)) -
+ dequeueBufferDuration;
+ if (actualDuration > kSanityCheckLowerBound && actualDuration < kSanityCheckUpperBound) {
+ mHintSessionWrapper->reportActualWorkDuration(actualDuration);
+ }
}
mLastDequeueBufferDuration = dequeueBufferDuration;
diff --git a/location/java/android/location/GnssCapabilities.java b/location/java/android/location/GnssCapabilities.java
index 7a412a0..b38f9ea 100644
--- a/location/java/android/location/GnssCapabilities.java
+++ b/location/java/android/location/GnssCapabilities.java
@@ -207,7 +207,7 @@
/**
* Returns {@code true} if GNSS chipset supports single shot locating, {@code false} otherwise.
*/
- public boolean hasSingleShot() {
+ public boolean hasSingleShotFix() {
return (mTopFlags & TOP_HAL_CAPABILITY_SINGLE_SHOT) != 0;
}
@@ -482,7 +482,7 @@
if (hasMsa()) {
builder.append("MSA ");
}
- if (hasSingleShot()) {
+ if (hasSingleShotFix()) {
builder.append("SINGLE_SHOT ");
}
if (hasOnDemandTime()) {
@@ -602,7 +602,7 @@
/**
* Sets single shot locating capability.
*/
- public @NonNull Builder setHasSingleShot(boolean capable) {
+ public @NonNull Builder setHasSingleShotFix(boolean capable) {
mTopFlags = setFlag(mTopFlags, TOP_HAL_CAPABILITY_SINGLE_SHOT, capable);
return this;
}
diff --git a/location/java/android/location/GnssStatus.java b/location/java/android/location/GnssStatus.java
index 23390fc..09f40e8 100644
--- a/location/java/android/location/GnssStatus.java
+++ b/location/java/android/location/GnssStatus.java
@@ -199,15 +199,15 @@
* <li>93-106 as the frequency channel number (FCN) (-7 to +6) plus 100.
* i.e. encode FCN of -7 as 93, 0 as 100, and +6 as 106</li>
* </ul></li>
- * <li>QZSS: 193-200</li>
+ * <li>QZSS: 183-206</li>
* <li>Galileo: 1-36</li>
- * <li>Beidou: 1-37</li>
+ * <li>Beidou: 1-63</li>
* <li>IRNSS: 1-14</li>
* </ul>
*
* @param satelliteIndex An index from zero to {@link #getSatelliteCount()} - 1
*/
- @IntRange(from = 1, to = 200)
+ @IntRange(from = 1, to = 206)
public int getSvid(@IntRange(from = 0) int satelliteIndex) {
return mSvidWithFlags[satelliteIndex] >> SVID_SHIFT_WIDTH;
}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java
index 24cb5f9..602e31c 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java
@@ -85,7 +85,7 @@
SliceStoreBroadcastReceiver.updateSliceStoreActivity(mCapability, this);
- mWebView = new WebView(getApplicationContext());
+ mWebView = new WebView(this);
setContentView(mWebView);
mWebView.loadUrl(mUrl.toString());
// TODO(b/245882601): Get back response from WebView
diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml
index 428f2dc..2000d96 100644
--- a/packages/CompanionDeviceManager/res/values/styles.xml
+++ b/packages/CompanionDeviceManager/res/values/styles.xml
@@ -49,7 +49,6 @@
<style name="DescriptionSummary">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
- <item name="android:gravity">center</item>
<item name="android:layout_marginTop">18dp</item>
<item name="android:layout_marginLeft">18dp</item>
<item name="android:layout_marginRight">18dp</item>
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index e037db7..2ba8748 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -35,7 +35,7 @@
ProviderInfo(
// TODO: replace to extract from the service data structure when available
icon = context.getDrawable(R.drawable.ic_passkey)!!,
- name = it.providerId,
+ name = it.providerFlattenedComponentName,
displayName = it.providerDisplayName,
credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
credentialOptions = toCredentialOptionInfoList(it.credentialEntries, context)
@@ -79,7 +79,7 @@
com.android.credentialmanager.createflow.ProviderInfo(
// TODO: replace to extract from the service data structure when available
icon = context.getDrawable(R.drawable.ic_passkey)!!,
- name = it.providerId,
+ name = it.providerFlattenedComponentName,
displayName = it.providerDisplayName,
credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
createOptions = toCreationOptionInfoList(it.credentialEntries, context),
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index 2820ed7..3b159e9 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -59,9 +59,9 @@
api "androidx.compose.material:material-icons-extended:$jetpack_compose_version"
api "androidx.compose.runtime:runtime-livedata:$jetpack_compose_version"
api "androidx.compose.ui:ui-tooling-preview:$jetpack_compose_version"
- api "androidx.lifecycle:lifecycle-livedata-ktx:2.6.0-alpha02"
+ api "androidx.lifecycle:lifecycle-livedata-ktx:2.6.0-alpha03"
api "androidx.navigation:navigation-compose:2.5.0"
- api "com.google.android.material:material:1.6.1"
+ api "com.google.android.material:material:1.7.0-alpha03"
debugApi "androidx.compose.ui:ui-tooling:$jetpack_compose_version"
implementation "com.airbnb.android:lottie-compose:5.2.0"
}
diff --git a/packages/SettingsLib/Spa/spa/res/values-night/themes.xml b/packages/SettingsLib/Spa/spa/res/values-night/themes.xml
deleted file mode 100644
index 67dd2b0..0000000
--- a/packages/SettingsLib/Spa/spa/res/values-night/themes.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2022 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.
--->
-<resources>
-
- <style name="Theme.SpaLib.DayNight" />
-</resources>
diff --git a/packages/SettingsLib/Spa/spa/res/values/themes.xml b/packages/SettingsLib/Spa/spa/res/values/themes.xml
index e0e5fc2..25846ec 100644
--- a/packages/SettingsLib/Spa/spa/res/values/themes.xml
+++ b/packages/SettingsLib/Spa/spa/res/values/themes.xml
@@ -16,12 +16,10 @@
-->
<resources>
- <style name="Theme.SpaLib" parent="Theme.Material3.DayNight.NoActionBar">
+ <style name="Theme.SpaLib" parent="@android:style/Theme.DeviceDefault.Settings">
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
- </style>
-
- <style name="Theme.SpaLib.DayNight">
- <item name="android:windowLightStatusBar">true</item>
+ <item name="android:windowActionBar">false</item>
+ <item name="android:windowNoTitle">true</item>
</style>
</resources>
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index d3efaa7..c3c90ab 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -27,6 +27,7 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.core.view.WindowCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.navigation.NavGraph.Companion.findStartDestination
@@ -66,8 +67,9 @@
private val spaEnvironment get() = SpaEnvironmentFactory.instance
override fun onCreate(savedInstanceState: Bundle?) {
- setTheme(R.style.Theme_SpaLib_DayNight)
+ setTheme(R.style.Theme_SpaLib)
super.onCreate(savedInstanceState)
+ WindowCompat.setDecorFitsSystemWindows(window, false)
spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK)
setContent {
@@ -83,35 +85,19 @@
val navController = rememberNavController()
val nullPage = SettingsPage.createNull()
CompositionLocalProvider(navController.localNavController()) {
- NavHost(navController, nullPage.sppName) {
+ NavHost(
+ navController = navController,
+ startDestination = nullPage.sppName,
+ ) {
composable(nullPage.sppName) {}
for (spp in sppRepository.getAllProviders()) {
composable(
route = spp.name + spp.parameter.navRoute(),
arguments = spp.parameter,
) { navBackStackEntry ->
- val lifecycleOwner = LocalLifecycleOwner.current
- val sp = remember(navBackStackEntry.arguments) {
+ PageLogger(remember(navBackStackEntry.arguments) {
spp.createSettingsPage(arguments = navBackStackEntry.arguments)
- }
-
- DisposableEffect(lifecycleOwner) {
- val observer = LifecycleEventObserver { _, event ->
- if (event == Lifecycle.Event.ON_START) {
- sp.enterPage()
- } else if (event == Lifecycle.Event.ON_STOP) {
- sp.leavePage()
- }
- }
-
- // Add the observer to the lifecycle
- lifecycleOwner.lifecycle.addObserver(observer)
-
- // When the effect leaves the Composition, remove the observer
- onDispose {
- lifecycleOwner.lifecycle.removeObserver(observer)
- }
- }
+ })
spp.Page(navBackStackEntry.arguments)
}
@@ -122,6 +108,28 @@
}
@Composable
+ private fun PageLogger(settingsPage: SettingsPage) {
+ val lifecycleOwner = LocalLifecycleOwner.current
+ DisposableEffect(lifecycleOwner) {
+ val observer = LifecycleEventObserver { _, event ->
+ if (event == Lifecycle.Event.ON_START) {
+ settingsPage.enterPage()
+ } else if (event == Lifecycle.Event.ON_STOP) {
+ settingsPage.leavePage()
+ }
+ }
+
+ // Add the observer to the lifecycle
+ lifecycleOwner.lifecycle.addObserver(observer)
+
+ // When the effect leaves the Composition, remove the observer
+ onDispose {
+ lifecycleOwner.lifecycle.removeObserver(observer)
+ }
+ }
+ }
+
+ @Composable
private fun InitialDestinationNavigator() {
val sppRepository by spaEnvironment.pageProviderRepository
val destinationNavigated = rememberSaveable { mutableStateOf(false) }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PaddingValuesExt.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PaddingValuesExt.kt
new file mode 100644
index 0000000..18335ff
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PaddingValuesExt.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.framework.compose
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+
+internal fun PaddingValues.horizontalValues(): PaddingValues = HorizontalPaddingValues(this)
+
+internal fun PaddingValues.verticalValues(): PaddingValues = VerticalPaddingValues(this)
+
+private class HorizontalPaddingValues(private val paddingValues: PaddingValues) : PaddingValues {
+ override fun calculateLeftPadding(layoutDirection: LayoutDirection) =
+ paddingValues.calculateLeftPadding(layoutDirection)
+
+ override fun calculateTopPadding(): Dp = 0.dp
+
+ override fun calculateRightPadding(layoutDirection: LayoutDirection) =
+ paddingValues.calculateRightPadding(layoutDirection)
+
+ override fun calculateBottomPadding() = 0.dp
+}
+
+private class VerticalPaddingValues(private val paddingValues: PaddingValues) : PaddingValues {
+ override fun calculateLeftPadding(layoutDirection: LayoutDirection) = 0.dp
+
+ override fun calculateTopPadding(): Dp = paddingValues.calculateTopPadding()
+
+ override fun calculateRightPadding(layoutDirection: LayoutDirection) = 0.dp
+
+ override fun calculateBottomPadding() = paddingValues.calculateBottomPadding()
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt
index 9eaa88a..26491d5 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt
@@ -62,7 +62,7 @@
private val spaEnvironment get() = SpaEnvironmentFactory.instance
override fun onCreate(savedInstanceState: Bundle?) {
- setTheme(R.style.Theme_SpaLib_DayNight)
+ setTheme(R.style.Theme_SpaLib)
super.onCreate(savedInstanceState)
spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt
index eb20ac5..711c8a7 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt
@@ -20,6 +20,7 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
@@ -34,6 +35,7 @@
Modifier
.fillMaxSize()
.background(color = MaterialTheme.colorScheme.background)
+ .systemBarsPadding()
.verticalScroll(rememberScrollState()),
) {
Text(
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt
index 9a17b2a..d17a8dc 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt
@@ -19,7 +19,7 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Scaffold
@@ -27,6 +27,8 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
/**
* A [Scaffold] which content is scrollable and wrapped in a [Column].
@@ -42,8 +44,9 @@
) {
SettingsScaffold(title, actions) { paddingValues ->
Column(Modifier.verticalScroll(rememberScrollState())) {
- Spacer(Modifier.padding(paddingValues))
+ Spacer(Modifier.height(paddingValues.calculateTopPadding()))
content()
+ Spacer(Modifier.height(paddingValues.calculateBottomPadding()))
}
}
}
@@ -52,6 +55,13 @@
@Composable
private fun RegularScaffoldPreview() {
SettingsTheme {
- RegularScaffold(title = "Display") {}
+ RegularScaffold(title = "Display") {
+ Preference(object : PreferenceModel {
+ override val title = "Item 1"
+ })
+ Preference(object : PreferenceModel {
+ override val title = "Item 2"
+ })
+ }
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
index 4f83ad6..efc623a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
@@ -14,13 +14,12 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalMaterial3Api::class)
-
package com.android.settingslib.spa.widget.scaffold
import androidx.activity.compose.BackHandler
import androidx.appcompat.R
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -31,10 +30,13 @@
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
@@ -48,45 +50,57 @@
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import com.android.settingslib.spa.framework.compose.hideKeyboardAction
+import com.android.settingslib.spa.framework.compose.horizontalValues
import com.android.settingslib.spa.framework.theme.SettingsOpacity
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
/**
* A [Scaffold] which content is can be full screen, and with a search feature built-in.
*/
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SearchScaffold(
title: String,
actions: @Composable RowScope.() -> Unit = {},
- content: @Composable (searchQuery: State<String>) -> Unit,
+ content: @Composable (bottomPadding: Dp, searchQuery: State<String>) -> Unit,
) {
val viewModel: SearchScaffoldViewModel = viewModel()
+ val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Scaffold(
+ modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
SearchableTopAppBar(
title = title,
actions = actions,
+ scrollBehavior = scrollBehavior,
searchQuery = viewModel.searchQuery,
) { viewModel.searchQuery = it }
},
) { paddingValues ->
Box(
Modifier
- .padding(paddingValues)
- .fillMaxSize()
+ .padding(paddingValues.horizontalValues())
+ .padding(top = paddingValues.calculateTopPadding())
+ .fillMaxSize(),
) {
- val searchQuery = remember {
- derivedStateOf { viewModel.searchQuery?.text ?: "" }
- }
- content(searchQuery)
+ content(
+ bottomPadding = paddingValues.calculateBottomPadding(),
+ searchQuery = remember {
+ derivedStateOf { viewModel.searchQuery?.text ?: "" }
+ },
+ )
}
}
}
@@ -95,10 +109,12 @@
var searchQuery: TextFieldValue? by mutableStateOf(null)
}
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun SearchableTopAppBar(
title: String,
actions: @Composable RowScope.() -> Unit,
+ scrollBehavior: TopAppBarScrollBehavior,
searchQuery: TextFieldValue?,
onSearchQueryChange: (TextFieldValue?) -> Unit,
) {
@@ -110,13 +126,17 @@
actions = actions,
)
} else {
- SettingsTopAppBar(title) {
- SearchAction { onSearchQueryChange(TextFieldValue()) }
+ SettingsTopAppBar(title, scrollBehavior) {
+ SearchAction {
+ scrollBehavior.collapse()
+ onSearchQueryChange(TextFieldValue())
+ }
actions()
}
}
}
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun SearchTopAppBar(
query: TextFieldValue,
@@ -124,21 +144,24 @@
onClose: () -> Unit,
actions: @Composable RowScope.() -> Unit = {},
) {
- TopAppBar(
- title = { SearchBox(query, onQueryChange) },
- modifier = Modifier.statusBarsPadding(),
- navigationIcon = { CollapseAction(onClose) },
- actions = {
- if (query.text.isNotEmpty()) {
- ClearAction { onQueryChange(TextFieldValue()) }
- }
- actions()
- },
- colors = settingsTopAppBarColors(),
- )
+ Surface(color = SettingsTheme.colorScheme.surfaceHeader) {
+ TopAppBar(
+ title = { SearchBox(query, onQueryChange) },
+ modifier = Modifier.statusBarsPadding(),
+ navigationIcon = { CollapseAction(onClose) },
+ actions = {
+ if (query.text.isNotEmpty()) {
+ ClearAction { onQueryChange(TextFieldValue()) }
+ }
+ actions()
+ },
+ colors = TopAppBarDefaults.smallTopAppBarColors(containerColor = Color.Transparent),
+ )
+ }
BackHandler { onClose() }
}
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun SearchBox(query: TextFieldValue, onQueryChange: (TextFieldValue) -> Unit) {
val focusRequester = remember { FocusRequester() }
@@ -184,6 +207,15 @@
@Composable
private fun SearchScaffoldPreview() {
SettingsTheme {
- SearchScaffold(title = "App notifications") {}
+ SearchScaffold(title = "App notifications") { _, _ ->
+ Column {
+ Preference(object : PreferenceModel {
+ override val title = "Item 1"
+ })
+ Preference(object : PreferenceModel {
+ override val title = "Item 2"
+ })
+ }
+ }
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
index 3bc3dd7..f4e504a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
@@ -16,13 +16,23 @@
package com.android.settingslib.spa.widget.scaffold
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
+import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.compose.horizontalValues
+import com.android.settingslib.spa.framework.compose.verticalValues
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
/**
* A [Scaffold] which content is can be full screen when needed.
@@ -34,16 +44,30 @@
actions: @Composable RowScope.() -> Unit = {},
content: @Composable (PaddingValues) -> Unit,
) {
+ val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Scaffold(
- topBar = { SettingsTopAppBar(title, actions) },
- content = content,
- )
+ modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
+ topBar = { SettingsTopAppBar(title, scrollBehavior, actions) },
+ ) { paddingValues ->
+ Box(Modifier.padding(paddingValues.horizontalValues())) {
+ content(paddingValues.verticalValues())
+ }
+ }
}
@Preview
@Composable
private fun SettingsScaffoldPreview() {
SettingsTheme {
- SettingsScaffold(title = "Display") {}
+ SettingsScaffold(title = "Display") { paddingValues ->
+ Column(Modifier.padding(paddingValues)) {
+ Preference(object : PreferenceModel {
+ override val title = "Item 1"
+ })
+ Preference(object : PreferenceModel {
+ override val title = "Item 2"
+ })
+ }
+ }
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTopAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTopAppBar.kt
index 9353520..f7cb035 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTopAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTopAppBar.kt
@@ -17,41 +17,70 @@
package com.android.settingslib.spa.widget.scaffold
import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.LargeTopAppBar
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
-import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
+import com.android.settingslib.spa.framework.compose.horizontalValues
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.theme.rememberSettingsTypography
@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun SettingsTopAppBar(
title: String,
+ scrollBehavior: TopAppBarScrollBehavior,
actions: @Composable RowScope.() -> Unit,
) {
- TopAppBar(
- title = {
- Text(
- text = title,
- modifier = Modifier.padding(SettingsDimension.itemPaddingAround),
- overflow = TextOverflow.Ellipsis,
- maxLines = 1,
- )
- },
- navigationIcon = { NavigateBack() },
- actions = actions,
- colors = settingsTopAppBarColors(),
+ val colorScheme = MaterialTheme.colorScheme
+ // TODO: Remove MaterialTheme() after top app bar color fixed in AndroidX.
+ MaterialTheme(
+ colorScheme = remember { colorScheme.copy(surface = colorScheme.background) },
+ typography = rememberSettingsTypography(),
+ ) {
+ LargeTopAppBar(
+ title = { Title(title) },
+ navigationIcon = { NavigateBack() },
+ actions = actions,
+ colors = largeTopAppBarColors(),
+ scrollBehavior = scrollBehavior,
+ )
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+internal fun TopAppBarScrollBehavior.collapse() {
+ with(state) {
+ heightOffset = heightOffsetLimit
+ }
+}
+
+@Composable
+private fun Title(title: String) {
+ Text(
+ text = title,
+ modifier = Modifier
+ .padding(WindowInsets.navigationBars.asPaddingValues().horizontalValues())
+ .padding(SettingsDimension.itemPaddingAround),
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-internal fun settingsTopAppBarColors() = TopAppBarDefaults.smallTopAppBarColors(
- containerColor = SettingsTheme.colorScheme.surfaceHeader,
+private fun largeTopAppBarColors() = TopAppBarDefaults.largeTopAppBarColors(
+ containerColor = MaterialTheme.colorScheme.background,
scrolledContainerColor = SettingsTheme.colorScheme.surfaceHeader,
)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/util/EntryHighlight.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/util/EntryHighlight.kt
index 652e54d..d09aec9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/util/EntryHighlight.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/util/EntryHighlight.kt
@@ -16,21 +16,42 @@
package com.android.settingslib.spa.widget.util
+import androidx.compose.animation.animateColorAsState
+import androidx.compose.animation.core.RepeatMode
+import androidx.compose.animation.core.repeatable
+import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
import com.android.settingslib.spa.framework.common.LocalEntryDataProvider
+import com.android.settingslib.spa.framework.theme.SettingsTheme
@Composable
internal fun EntryHighlight(UiLayoutFn: @Composable () -> Unit) {
val entryData = LocalEntryDataProvider.current
- val isHighlighted = rememberSaveable { entryData.isHighlighted }
- val backgroundColor =
- if (isHighlighted) MaterialTheme.colorScheme.surfaceVariant else Color.Transparent
+ var isHighlighted by rememberSaveable { mutableStateOf(false) }
+ SideEffect {
+ isHighlighted = entryData.isHighlighted
+ }
+
+ val backgroundColor by animateColorAsState(
+ targetValue = when {
+ isHighlighted -> MaterialTheme.colorScheme.surfaceVariant
+ else -> SettingsTheme.colorScheme.background
+ },
+ animationSpec = repeatable(
+ iterations = 3,
+ animation = tween(durationMillis = 500),
+ repeatMode = RepeatMode.Restart
+ )
+ )
Box(modifier = Modifier.background(color = backgroundColor)) {
UiLayoutFn()
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/RegularScaffoldTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/RegularScaffoldTest.kt
new file mode 100644
index 0000000..1964c43
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/RegularScaffoldTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.widget.scaffold
+
+import androidx.compose.material3.Text
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class RegularScaffoldTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun regularScaffold_titleIsDisplayed() {
+ composeTestRule.setContent {
+ RegularScaffold(title = TITLE) {
+ Text(text = "AAA")
+ Text(text = "BBB")
+ }
+ }
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+ }
+
+ @Test
+ fun regularScaffold_itemsAreDisplayed() {
+ composeTestRule.setContent {
+ RegularScaffold(title = TITLE) {
+ Text(text = "AAA")
+ Text(text = "BBB")
+ }
+ }
+
+ composeTestRule.onNodeWithText("AAA").assertIsDisplayed()
+ composeTestRule.onNodeWithText("BBB").assertIsDisplayed()
+ }
+
+ private companion object {
+ const val TITLE = "title"
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SearchScaffoldTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SearchScaffoldTest.kt
index ec3379d..c3e1d54 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SearchScaffoldTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SearchScaffoldTest.kt
@@ -43,7 +43,7 @@
@Test
fun initialState_titleIsDisplayed() {
composeTestRule.setContent {
- SearchScaffold(title = TITLE) {}
+ SearchScaffold(title = TITLE) { _, _ -> }
}
composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
@@ -116,7 +116,7 @@
private fun setContent(): State<String> {
lateinit var actualSearchQuery: State<String>
composeTestRule.setContent {
- SearchScaffold(title = TITLE) { searchQuery ->
+ SearchScaffold(title = TITLE) { _, searchQuery ->
SideEffect {
actualSearchQuery = searchQuery
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerTest.kt
similarity index 89%
rename from packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt
rename to packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerTest.kt
index 0c84eac..0c745d5 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerTest.kt
@@ -16,7 +16,6 @@
package com.android.settingslib.spa.widget.scaffold
-import androidx.compose.runtime.Composable
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertIsNotSelected
@@ -31,15 +30,13 @@
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
-class SettingsPagerKtTest {
+class SettingsPagerTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun twoPage_initialState() {
- composeTestRule.setContent {
- TestTwoPage()
- }
+ setTwoPagesContent()
composeTestRule.onNodeWithText("Personal").assertIsSelected()
composeTestRule.onNodeWithText("Page 0").assertIsDisplayed()
@@ -49,9 +46,7 @@
@Test
fun twoPage_afterSwitch() {
- composeTestRule.setContent {
- TestTwoPage()
- }
+ setTwoPagesContent()
composeTestRule.onNodeWithText("Work").performClick()
@@ -73,11 +68,12 @@
composeTestRule.onNodeWithText("Page 0").assertIsDisplayed()
composeTestRule.onNodeWithText("Page 1").assertDoesNotExist()
}
-}
-@Composable
-private fun TestTwoPage() {
- SettingsPager(listOf("Personal", "Work")) {
- SettingsTitle(title = "Page $it")
+ private fun setTwoPagesContent() {
+ composeTestRule.setContent {
+ SettingsPager(listOf("Personal", "Work")) {
+ SettingsTitle(title = "Page $it")
+ }
+ }
}
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffoldTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffoldTest.kt
new file mode 100644
index 0000000..f042404
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffoldTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.widget.scaffold
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.material3.Text
+import androidx.compose.runtime.SideEffect
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsScaffoldTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun settingsScaffold_titleIsDisplayed() {
+ composeTestRule.setContent {
+ SettingsScaffold(title = TITLE) {
+ Text(text = "AAA")
+ Text(text = "BBB")
+ }
+ }
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+ }
+
+ @Test
+ fun settingsScaffold_itemsAreDisplayed() {
+ composeTestRule.setContent {
+ SettingsScaffold(title = TITLE) {
+ Text(text = "AAA")
+ Text(text = "BBB")
+ }
+ }
+
+ composeTestRule.onNodeWithText("AAA").assertIsDisplayed()
+ composeTestRule.onNodeWithText("BBB").assertIsDisplayed()
+ }
+
+ @Test
+ fun settingsScaffold_noHorizontalPadding() {
+ lateinit var actualPaddingValues: PaddingValues
+
+ composeTestRule.setContent {
+ SettingsScaffold(title = TITLE) { paddingValues ->
+ SideEffect {
+ actualPaddingValues = paddingValues
+ }
+ }
+ }
+
+ assertThat(actualPaddingValues.calculateLeftPadding(LayoutDirection.Ltr)).isEqualTo(0.dp)
+ assertThat(actualPaddingValues.calculateLeftPadding(LayoutDirection.Rtl)).isEqualTo(0.dp)
+ assertThat(actualPaddingValues.calculateRightPadding(LayoutDirection.Ltr)).isEqualTo(0.dp)
+ assertThat(actualPaddingValues.calculateRightPadding(LayoutDirection.Rtl)).isEqualTo(0.dp)
+ }
+
+ private companion object {
+ const val TITLE = "title"
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfos.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfos.kt
index c1ac5d4..8954d22 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfos.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfos.kt
@@ -35,6 +35,9 @@
/** Checks whether a flag is associated with the application. */
fun ApplicationInfo.hasFlag(flag: Int): Boolean = (flags and flag) > 0
+/** Checks whether the application is currently installed. */
+val ApplicationInfo.installed: Boolean get() = hasFlag(ApplicationInfo.FLAG_INSTALLED)
+
/** Checks whether the application is disabled until used. */
val ApplicationInfo.isDisabledUntilUsed: Boolean
get() = enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index 408b9df..3cd8378 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -25,12 +25,12 @@
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.Dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.android.settingslib.spa.framework.compose.LogCompositions
import com.android.settingslib.spa.framework.compose.TimeMeasurer.Companion.rememberTimeMeasurer
import com.android.settingslib.spa.framework.compose.rememberLazyListStateAndHideKeyboardWhenStartScroll
import com.android.settingslib.spa.framework.compose.toState
-import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.ui.PlaceholderTitle
import com.android.settingslib.spaprivileged.R
import com.android.settingslib.spaprivileged.model.app.AppListConfig
@@ -55,10 +55,11 @@
option: State<Int>,
searchQuery: State<String>,
appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
+ bottomPadding: Dp,
) {
LogCompositions(TAG, appListConfig.userId.toString())
val appListData = loadAppEntries(appListConfig, listModel, showSystem, option, searchQuery)
- AppListWidget(appListData, listModel, appItem)
+ AppListWidget(appListData, listModel, appItem, bottomPadding)
}
@Composable
@@ -66,6 +67,7 @@
appListData: State<AppListData<T>?>,
listModel: AppListModel<T>,
appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
+ bottomPadding: Dp,
) {
val timeMeasurer = rememberTimeMeasurer(TAG)
appListData.value?.let { (list, option) ->
@@ -77,7 +79,7 @@
LazyColumn(
modifier = Modifier.fillMaxSize(),
state = rememberLazyListStateAndHideKeyboardWhenStartScroll(),
- contentPadding = PaddingValues(bottom = SettingsDimension.itemPaddingVertical),
+ contentPadding = PaddingValues(bottom = bottomPadding),
) {
items(count = list.size, key = { option to list[it].record.app.packageName }) {
val appEntry = list[it]
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index 99376b0..2953367 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -52,7 +52,7 @@
actions = {
ShowSystemAction(showSystem.value) { showSystem.value = it }
},
- ) { searchQuery ->
+ ) { bottomPadding, searchQuery ->
WorkProfilePager(primaryUserOnly) { userInfo ->
Column(Modifier.fillMaxSize()) {
val options = remember { listModel.getSpinnerOptions() }
@@ -68,6 +68,7 @@
option = selectedOption,
searchQuery = searchQuery,
appItem = appItem,
+ bottomPadding = bottomPadding,
)
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
index 03d9f2d..30d3820 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
@@ -357,5 +357,12 @@
* {@link SubscriptionManager#getDefaultSubscriptionId()}.
*/
public static final String COLUMN_IS_DEFAULT_SUBSCRIPTION = "isDefaultSubscription";
+
+ /**
+ * The name of the active data subscription state column, see
+ * {@link SubscriptionManager#getActiveDataSubscriptionId()}.
+ */
+ public static final String COLUMN_IS_ACTIVE_DATA_SUBSCRIPTION =
+ "isActiveDataSubscriptionId";
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
index 329bd9b..23566f7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
@@ -42,7 +42,7 @@
boolean isUsableSubscription, boolean isActiveSubscriptionId,
boolean isAvailableSubscription, boolean isDefaultVoiceSubscription,
boolean isDefaultSmsSubscription, boolean isDefaultDataSubscription,
- boolean isDefaultSubscription) {
+ boolean isDefaultSubscription, boolean isActiveDataSubscriptionId) {
this.subId = subId;
this.simSlotIndex = simSlotIndex;
this.carrierId = carrierId;
@@ -72,6 +72,7 @@
this.isDefaultSmsSubscription = isDefaultSmsSubscription;
this.isDefaultDataSubscription = isDefaultDataSubscription;
this.isDefaultSubscription = isDefaultSubscription;
+ this.isActiveDataSubscriptionId = isActiveDataSubscriptionId;
}
@PrimaryKey
@@ -165,6 +166,9 @@
@ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_DEFAULT_SUBSCRIPTION)
public boolean isDefaultSubscription;
+ @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_ACTIVE_DATA_SUBSCRIPTION)
+ public boolean isActiveDataSubscriptionId;
+
public int getSubId() {
return Integer.valueOf(subId);
}
@@ -213,6 +217,7 @@
result = 31 * result + Boolean.hashCode(isDefaultSmsSubscription);
result = 31 * result + Boolean.hashCode(isDefaultDataSubscription);
result = 31 * result + Boolean.hashCode(isDefaultSubscription);
+ result = 31 * result + Boolean.hashCode(isActiveDataSubscriptionId);
return result;
}
@@ -254,7 +259,8 @@
&& isDefaultVoiceSubscription == info.isDefaultVoiceSubscription
&& isDefaultSmsSubscription == info.isDefaultSmsSubscription
&& isDefaultDataSubscription == info.isDefaultDataSubscription
- && isDefaultSubscription == info.isDefaultSubscription;
+ && isDefaultSubscription == info.isDefaultSubscription
+ && isActiveDataSubscriptionId == info.isActiveDataSubscriptionId;
}
public String toString() {
@@ -317,6 +323,8 @@
.append(isDefaultDataSubscription)
.append(", isDefaultSubscription = ")
.append(isDefaultSubscription)
+ .append(", isActiveDataSubscriptionId = ")
+ .append(isActiveDataSubscriptionId)
.append(")}");
return builder.toString();
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index ccbfac2..fa96a2f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -5533,13 +5533,17 @@
}
if (currentVersion == 210) {
final SettingsState secureSettings = getSecureSettingsLocked(userId);
- final int defaultValueVibrateIconEnabled = getContext().getResources()
- .getInteger(R.integer.def_statusBarVibrateIconEnabled);
- secureSettings.insertSettingOverrideableByRestoreLocked(
- Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
- String.valueOf(defaultValueVibrateIconEnabled),
- null /* tag */, true /* makeDefault */,
- SettingsState.SYSTEM_PACKAGE_NAME);
+ final Setting currentSetting = secureSettings.getSettingLocked(
+ Secure.STATUS_BAR_SHOW_VIBRATE_ICON);
+ if (currentSetting.isNull()) {
+ final int defaultValueVibrateIconEnabled = getContext().getResources()
+ .getInteger(R.integer.def_statusBarVibrateIconEnabled);
+ secureSettings.insertSettingOverrideableByRestoreLocked(
+ Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
+ String.valueOf(defaultValueVibrateIconEnabled),
+ null /* tag */, true /* makeDefault */,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ }
currentVersion = 211;
}
// vXXX: Add new settings above this point.
diff --git a/packages/SystemUI/docs/device-entry/quickaffordance.md b/packages/SystemUI/docs/device-entry/quickaffordance.md
index 38d636d7..95b986f 100644
--- a/packages/SystemUI/docs/device-entry/quickaffordance.md
+++ b/packages/SystemUI/docs/device-entry/quickaffordance.md
@@ -8,7 +8,7 @@
### Step 1: create a new quick affordance config
* Create a new class under the [systemui/keyguard/domain/quickaffordance](../../src/com/android/systemui/keyguard/domain/quickaffordance) directory
* Please make sure that the class is injected through the Dagger dependency injection system by using the `@Inject` annotation on its main constructor and the `@SysUISingleton` annotation at class level, to make sure only one instance of the class is ever instantiated
-* Have the class implement the [KeyguardQuickAffordanceConfig](../../src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt) interface, notes:
+* Have the class implement the [KeyguardQuickAffordanceConfig](../../src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt) interface, notes:
* The `state` Flow property must emit `State.Hidden` when the feature is not enabled!
* It is safe to assume that `onQuickAffordanceClicked` will not be invoked if-and-only-if the previous rule is followed
* When implementing `onQuickAffordanceClicked`, the implementation can do something or it can ask the framework to start an activity using an `Intent` provided by the implementation
diff --git a/packages/SystemUI/res/layout-land/auth_credential_password_view.xml b/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
index 3bcc37a..e2ce34f 100644
--- a/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
+++ b/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
@@ -14,7 +14,7 @@
~ limitations under the License.
-->
-<com.android.systemui.biometrics.AuthCredentialPasswordView
+<com.android.systemui.biometrics.ui.CredentialPasswordView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
@@ -86,4 +86,4 @@
</LinearLayout>
-</com.android.systemui.biometrics.AuthCredentialPasswordView>
\ No newline at end of file
+</com.android.systemui.biometrics.ui.CredentialPasswordView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
index a3dd334..6e0e38b 100644
--- a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
@@ -14,7 +14,7 @@
~ limitations under the License.
-->
-<com.android.systemui.biometrics.AuthCredentialPatternView
+<com.android.systemui.biometrics.ui.CredentialPatternView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
@@ -83,4 +83,4 @@
</FrameLayout>
-</com.android.systemui.biometrics.AuthCredentialPatternView>
\ No newline at end of file
+</com.android.systemui.biometrics.ui.CredentialPatternView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_credential_password_view.xml b/packages/SystemUI/res/layout/auth_credential_password_view.xml
index 774b335f..021ebe6 100644
--- a/packages/SystemUI/res/layout/auth_credential_password_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_password_view.xml
@@ -14,7 +14,7 @@
~ limitations under the License.
-->
-<com.android.systemui.biometrics.AuthCredentialPasswordView
+<com.android.systemui.biometrics.ui.CredentialPasswordView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
@@ -83,4 +83,4 @@
</LinearLayout>
-</com.android.systemui.biometrics.AuthCredentialPasswordView>
\ No newline at end of file
+</com.android.systemui.biometrics.ui.CredentialPasswordView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
index 4af9970..891c6af 100644
--- a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
@@ -14,7 +14,7 @@
~ limitations under the License.
-->
-<com.android.systemui.biometrics.AuthCredentialPatternView
+<com.android.systemui.biometrics.ui.CredentialPatternView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
@@ -78,4 +78,4 @@
android:layout_gravity="center_horizontal|bottom"/>
</FrameLayout>
-</com.android.systemui.biometrics.AuthCredentialPatternView>
\ No newline at end of file
+</com.android.systemui.biometrics.ui.CredentialPatternView>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 92ef3f8..159323a 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -42,12 +42,6 @@
android:clipToPadding="false"
android:clipChildren="false">
- <ViewStub
- android:id="@+id/qs_header_stub"
- android:layout_height="wrap_content"
- android:layout_width="match_parent"
- />
-
<include
layout="@layout/keyguard_status_view"
android:visibility="gone"/>
@@ -69,6 +63,15 @@
systemui:layout_constraintBottom_toBottomOf="parent"
/>
+ <!-- This view should be after qs_frame so touches are dispatched first to it. That gives
+ it a chance to capture clicks before the NonInterceptingScrollView disallows all
+ intercepts -->
+ <ViewStub
+ android:id="@+id/qs_header_stub"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ />
+
<androidx.constraintlayout.widget.Guideline
android:id="@+id/qs_edge_guideline"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 7ca42f7..4fd25a9 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -177,6 +177,7 @@
<item type="id" name="action_move_bottom_right"/>
<item type="id" name="action_move_to_edge_and_hide"/>
<item type="id" name="action_move_out_edge_and_show"/>
+ <item type="id" name="action_remove_menu"/>
<!-- rounded corner view id -->
<item type="id" name="rounded_corner_top_left"/>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d5d99cc..b325c56 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2218,6 +2218,8 @@
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half">Move to edge and hide</string>
<!-- Action in accessibility menu to move the accessibility floating button out the edge and show. [CHAR LIMIT=36]-->
<string name="accessibility_floating_button_action_move_out_edge_and_show">Move out edge and show</string>
+ <!-- Action in accessibility menu to remove the accessibility floating menu view on the screen. [CHAR LIMIT=36]-->
+ <string name="accessibility_floating_button_action_remove_menu">Remove</string>
<!-- Action in accessibility menu to toggle on/off the accessibility feature. [CHAR LIMIT=30]-->
<string name="accessibility_floating_button_action_double_tap_to_toggle">toggle</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
index 7e42e1b..8ac1de8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
@@ -85,13 +85,12 @@
mTmpSourceRectF.set(sourceBounds);
mTmpDestinationRect.set(sourceBounds);
mTmpDestinationRect.inset(insets);
- // Scale by the shortest edge and offset such that the top/left of the scaled inset
- // source rect aligns with the top/left of the destination bounds
+ // Scale to the bounds no smaller than the destination and offset such that the top/left
+ // of the scaled inset source rect aligns with the top/left of the destination bounds
final float scale;
if (sourceRectHint.isEmpty() || sourceRectHint.width() == sourceBounds.width()) {
- scale = sourceBounds.width() <= sourceBounds.height()
- ? (float) destinationBounds.width() / sourceBounds.width()
- : (float) destinationBounds.height() / sourceBounds.height();
+ scale = Math.max((float) destinationBounds.width() / sourceBounds.width(),
+ (float) destinationBounds.height() / sourceBounds.height());
} else {
// scale by sourceRectHint if it's not edge-to-edge
final float endScale = sourceRectHint.width() <= sourceRectHint.height()
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
index bb3df8f..7b216017 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
@@ -18,17 +18,15 @@
import android.content.Context
import android.os.Handler
-import com.android.internal.statusbar.IStatusBarService
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlagsDebug.ALL_FLAGS
import com.android.systemui.util.settings.SettingsUtilModule
import dagger.Binds
import dagger.Module
import dagger.Provides
-import javax.inject.Named
@Module(includes = [
FeatureFlagsDebugStartableModule::class,
+ FlagsCommonModule::class,
ServerFlagReaderModule::class,
SettingsUtilModule::class,
])
@@ -43,20 +41,5 @@
fun provideFlagManager(context: Context, @Main handler: Handler): FlagManager {
return FlagManager(context, handler)
}
-
- @JvmStatic
- @Provides
- @Named(ALL_FLAGS)
- fun providesAllFlags(): Map<Int, Flag<*>> = Flags.collectFlags()
-
- @JvmStatic
- @Provides
- fun providesRestarter(barService: IStatusBarService): Restarter {
- return object: Restarter {
- override fun restart() {
- barService.restart()
- }
- }
- }
}
}
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
index 0f7e732..aef8876 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
@@ -16,29 +16,15 @@
package com.android.systemui.flags
-import com.android.internal.statusbar.IStatusBarService
import dagger.Binds
import dagger.Module
-import dagger.Provides
@Module(includes = [
FeatureFlagsReleaseStartableModule::class,
+ FlagsCommonModule::class,
ServerFlagReaderModule::class
])
abstract class FlagsModule {
@Binds
abstract fun bindsFeatureFlagRelease(impl: FeatureFlagsRelease): FeatureFlags
-
- @Module
- companion object {
- @JvmStatic
- @Provides
- fun providesRestarter(barService: IStatusBarService): Restarter {
- return object: Restarter {
- override fun restart() {
- barService.restart()
- }
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 81305f9..0b395a8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -223,7 +223,7 @@
@Override
public void onSwipeUp() {
if (!mUpdateMonitor.isFaceDetectionRunning()) {
- boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(true,
+ boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(
FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
mKeyguardSecurityCallback.userActivity();
if (didFaceAuthRun) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index cc1f2fe..39dc609 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1615,7 +1615,7 @@
@Override
public void onUdfpsPointerDown(int sensorId) {
mLogger.logUdfpsPointerDown(sensorId);
- requestFaceAuth(true, FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
+ requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
}
/**
@@ -2357,14 +2357,12 @@
/**
* Requests face authentication if we're on a state where it's allowed.
* This will re-trigger auth in case it fails.
- * @param userInitiatedRequest true if the user explicitly requested face auth
* @param reason One of the reasons {@link FaceAuthApiRequestReason} on why this API is being
* invoked.
* @return current face auth detection state, true if it is running.
*/
- public boolean requestFaceAuth(boolean userInitiatedRequest,
- @FaceAuthApiRequestReason String reason) {
- mLogger.logFaceAuthRequested(userInitiatedRequest, reason);
+ public boolean requestFaceAuth(@FaceAuthApiRequestReason String reason) {
+ mLogger.logFaceAuthRequested(reason);
updateFaceListeningState(BIOMETRIC_ACTION_START, apiRequestReasonToUiEvent(reason));
return isFaceDetectionRunning();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 31fc320..3308f55 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -111,11 +111,10 @@
}, { "Face help received, msgId: $int1 msg: $str1" })
}
- fun logFaceAuthRequested(userInitiatedRequest: Boolean, reason: String?) {
+ fun logFaceAuthRequested(reason: String?) {
logBuffer.log(TAG, DEBUG, {
- bool1 = userInitiatedRequest
str1 = reason
- }, { "requestFaceAuth() userInitiated=$bool1 reason=$str1" })
+ }, { "requestFaceAuth() reason=$str1" })
}
fun logFaceAuthSuccess(userId: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
index f56a154..ee048e1 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
@@ -172,4 +172,8 @@
R.dimen.dismiss_circle_small);
mSizePercent = mMinDismissSize / maxDismissSize;
}
+
+ interface DismissCallback {
+ void onDismiss();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index eb25232..396f584 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -35,6 +35,8 @@
import androidx.dynamicanimation.animation.SpringForce;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.internal.util.Preconditions;
+
import java.util.HashMap;
/**
@@ -64,6 +66,7 @@
private final Handler mHandler;
private boolean mIsMovedToEdge;
private boolean mIsFadeEffectEnabled;
+ private DismissAnimationController.DismissCallback mDismissCallback;
// Cache the animations state of {@link DynamicAnimation.TRANSLATION_X} and {@link
// DynamicAnimation.TRANSLATION_Y} to be well controlled by the touch handler
@@ -102,6 +105,11 @@
}
}
+ void setDismissCallback(
+ DismissAnimationController.DismissCallback dismissCallback) {
+ mDismissCallback = dismissCallback;
+ }
+
void moveToTopLeftPosition() {
mIsMovedToEdge = false;
final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
@@ -132,6 +140,13 @@
constrainPositionAndUpdate(position);
}
+ void removeMenu() {
+ Preconditions.checkArgument(mDismissCallback != null,
+ "The dismiss callback should be initialized first.");
+
+ mDismissCallback.onDismiss();
+ }
+
void flingMenuThenSpringToEdge(float x, float velocityX, float velocityY) {
final boolean shouldMenuFlingLeft = isOnLeftSide()
? velocityX < ESCAPE_VELOCITY
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
index e69a248..ac5736b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
@@ -84,6 +84,12 @@
new AccessibilityNodeInfoCompat.AccessibilityActionCompat(moveEdgeId,
res.getString(moveEdgeTextResId));
info.addAction(moveToOrOutEdge);
+
+ final AccessibilityNodeInfoCompat.AccessibilityActionCompat removeMenu =
+ new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ R.id.action_remove_menu,
+ res.getString(R.string.accessibility_floating_button_action_remove_menu));
+ info.addAction(removeMenu);
}
@Override
@@ -126,6 +132,11 @@
return true;
}
+ if (action == R.id.action_remove_menu) {
+ mAnimationController.removeMenu();
+ return true;
+ }
+
return super.performAccessibilityAction(host, action, args);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 3e620a2..33e155d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -100,6 +100,7 @@
windowManager);
mMenuView = new MenuView(context, menuViewModel, menuViewAppearance);
mMenuAnimationController = mMenuView.getMenuAnimationController();
+ mMenuAnimationController.setDismissCallback(this::hideMenuAndShowMessage);
mDismissView = new DismissView(context);
mDismissAnimationController = new DismissAnimationController(mDismissView, mMenuView);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index b50bfd7..f74c721 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -26,6 +26,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AlertDialog;
import android.content.Context;
import android.graphics.PixelFormat;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
@@ -63,6 +64,9 @@
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.biometrics.AuthController.ScaleFactorProvider;
+import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
+import com.android.systemui.biometrics.ui.CredentialView;
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -74,11 +78,13 @@
import java.util.List;
import java.util.Set;
+import javax.inject.Provider;
+
/**
* Top level container/controller for the BiometricPrompt UI.
*/
public class AuthContainerView extends LinearLayout
- implements AuthDialog, WakefulnessLifecycle.Observer {
+ implements AuthDialog, WakefulnessLifecycle.Observer, CredentialView.Host {
private static final String TAG = "AuthContainerView";
@@ -112,15 +118,18 @@
private final IBinder mWindowToken = new Binder();
private final WindowManager mWindowManager;
private final Interpolator mLinearOutSlowIn;
- private final CredentialCallback mCredentialCallback;
private final LockPatternUtils mLockPatternUtils;
private final WakefulnessLifecycle mWakefulnessLifecycle;
private final InteractionJankMonitor mInteractionJankMonitor;
+ // TODO: these should be migrated out once ready
+ private final Provider<BiometricPromptCredentialInteractor> mBiometricPromptInteractor;
+ private final Provider<CredentialViewModel> mCredentialViewModelProvider;
+
@VisibleForTesting final BiometricCallback mBiometricCallback;
@Nullable private AuthBiometricView mBiometricView;
- @Nullable private AuthCredentialView mCredentialView;
+ @Nullable private View mCredentialView;
private final AuthPanelController mPanelController;
private final FrameLayout mFrameLayout;
private final ImageView mBackgroundView;
@@ -229,11 +238,13 @@
@NonNull WakefulnessLifecycle wakefulnessLifecycle,
@NonNull UserManager userManager,
@NonNull LockPatternUtils lockPatternUtils,
- @NonNull InteractionJankMonitor jankMonitor) {
+ @NonNull InteractionJankMonitor jankMonitor,
+ @NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor,
+ @NonNull Provider<CredentialViewModel> credentialViewModelProvider) {
mConfig.mSensorIds = sensorIds;
return new AuthContainerView(mConfig, fpProps, faceProps, wakefulnessLifecycle,
- userManager, lockPatternUtils, jankMonitor, new Handler(Looper.getMainLooper()),
- bgExecutor);
+ userManager, lockPatternUtils, jankMonitor, biometricPromptInteractor,
+ credentialViewModelProvider, new Handler(Looper.getMainLooper()), bgExecutor);
}
}
@@ -271,14 +282,51 @@
}
}
- final class CredentialCallback implements AuthCredentialView.Callback {
- @Override
- public void onCredentialMatched(byte[] attestation) {
- mCredentialAttestation = attestation;
- animateAway(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED);
+ @Override
+ public void onCredentialMatched(@NonNull byte[] attestation) {
+ mCredentialAttestation = attestation;
+ animateAway(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED);
+ }
+
+ @Override
+ public void onCredentialAborted() {
+ sendEarlyUserCanceled();
+ animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
+ }
+
+ @Override
+ public void onCredentialAttemptsRemaining(int remaining, @NonNull String messageBody) {
+ // Only show dialog if <=1 attempts are left before wiping.
+ if (remaining == 1) {
+ showLastAttemptBeforeWipeDialog(messageBody);
+ } else if (remaining <= 0) {
+ showNowWipingDialog(messageBody);
}
}
+ private void showLastAttemptBeforeWipeDialog(@NonNull String messageBody) {
+ final AlertDialog alertDialog = new AlertDialog.Builder(mContext)
+ .setTitle(R.string.biometric_dialog_last_attempt_before_wipe_dialog_title)
+ .setMessage(messageBody)
+ .setPositiveButton(android.R.string.ok, null)
+ .create();
+ alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+ alertDialog.show();
+ }
+
+ private void showNowWipingDialog(@NonNull String messageBody) {
+ final AlertDialog alertDialog = new AlertDialog.Builder(mContext)
+ .setMessage(messageBody)
+ .setPositiveButton(
+ com.android.settingslib.R.string.failed_attempts_now_wiping_dialog_dismiss,
+ null /* OnClickListener */)
+ .setOnDismissListener(
+ dialog -> animateAway(AuthDialogCallback.DISMISSED_ERROR))
+ .create();
+ alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+ alertDialog.show();
+ }
+
@VisibleForTesting
AuthContainerView(Config config,
@Nullable List<FingerprintSensorPropertiesInternal> fpProps,
@@ -287,6 +335,8 @@
@NonNull UserManager userManager,
@NonNull LockPatternUtils lockPatternUtils,
@NonNull InteractionJankMonitor jankMonitor,
+ @NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor,
+ @NonNull Provider<CredentialViewModel> credentialViewModelProvider,
@NonNull Handler mainHandler,
@NonNull @Background DelayableExecutor bgExecutor) {
super(config.mContext);
@@ -302,7 +352,6 @@
.getDimension(R.dimen.biometric_dialog_animation_translation_offset);
mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN;
mBiometricCallback = new BiometricCallback();
- mCredentialCallback = new CredentialCallback();
final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
mFrameLayout = (FrameLayout) layoutInflater.inflate(
@@ -314,6 +363,8 @@
mPanelController = new AuthPanelController(mContext, mPanelView);
mBackgroundExecutor = bgExecutor;
mInteractionJankMonitor = jankMonitor;
+ mBiometricPromptInteractor = biometricPromptInteractor;
+ mCredentialViewModelProvider = credentialViewModelProvider;
// Inflate biometric view only if necessary.
if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
@@ -404,12 +455,12 @@
switch (credentialType) {
case Utils.CREDENTIAL_PATTERN:
- mCredentialView = (AuthCredentialView) factory.inflate(
+ mCredentialView = factory.inflate(
R.layout.auth_credential_pattern_view, null, false);
break;
case Utils.CREDENTIAL_PIN:
case Utils.CREDENTIAL_PASSWORD:
- mCredentialView = (AuthCredentialView) factory.inflate(
+ mCredentialView = factory.inflate(
R.layout.auth_credential_password_view, null, false);
break;
default:
@@ -422,16 +473,12 @@
mBackgroundView.setOnClickListener(null);
mBackgroundView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
- mCredentialView.setContainerView(this);
- mCredentialView.setUserId(mConfig.mUserId);
- mCredentialView.setOperationId(mConfig.mOperationId);
- mCredentialView.setEffectiveUserId(mEffectiveUserId);
- mCredentialView.setCredentialType(credentialType);
- mCredentialView.setCallback(mCredentialCallback);
- mCredentialView.setPromptInfo(mConfig.mPromptInfo);
- mCredentialView.setPanelController(mPanelController, animatePanel);
- mCredentialView.setShouldAnimateContents(animateContents);
- mCredentialView.setBackgroundExecutor(mBackgroundExecutor);
+ mBiometricPromptInteractor.get().useCredentialsForAuthentication(
+ mConfig.mPromptInfo, credentialType, mConfig.mUserId, mConfig.mOperationId);
+ final CredentialViewModel vm = mCredentialViewModelProvider.get();
+ vm.setAnimateContents(animateContents);
+ ((CredentialView) mCredentialView).init(vm, this, mPanelController, animatePanel);
+
mFrameLayout.addView(mCredentialView);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 9c78df5..313ff4157 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -72,6 +72,8 @@
import com.android.internal.os.SomeArgs;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.CoreStartable;
+import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -122,6 +124,10 @@
private final Provider<UdfpsController> mUdfpsControllerFactory;
private final Provider<SidefpsController> mSidefpsControllerFactory;
+ // TODO: these should be migrated out once ready
+ @NonNull private final Provider<BiometricPromptCredentialInteractor> mBiometricPromptInteractor;
+ @NonNull private final Provider<CredentialViewModel> mCredentialViewModelProvider;
+
private final Display mDisplay;
private float mScaleFactor = 1f;
// sensor locations without any resolution scaling nor rotation adjustments:
@@ -693,6 +699,8 @@
@NonNull LockPatternUtils lockPatternUtils,
@NonNull UdfpsLogger udfpsLogger,
@NonNull StatusBarStateController statusBarStateController,
+ @NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor,
+ @NonNull Provider<CredentialViewModel> credentialViewModelProvider,
@NonNull InteractionJankMonitor jankMonitor,
@Main Handler handler,
@Background DelayableExecutor bgExecutor,
@@ -717,6 +725,9 @@
mFaceEnrolledForUser = new SparseBooleanArray();
mVibratorHelper = vibrator;
+ mBiometricPromptInteractor = biometricPromptInteractor;
+ mCredentialViewModelProvider = credentialViewModelProvider;
+
mOrientationListener = new BiometricDisplayListener(
context,
mDisplayManager,
@@ -1079,6 +1090,11 @@
return mUdfpsEnrolledForUser.get(userId);
}
+ /** If BiometricPrompt is currently being shown to the user. */
+ public boolean isShowing() {
+ return mCurrentDialog != null;
+ }
+
private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
mCurrentDialogArgs = args;
@@ -1210,7 +1226,8 @@
.setMultiSensorConfig(multiSensorConfig)
.setScaleFactorProvider(() -> getScaleFactor())
.build(bgExecutor, sensorIds, mFpProps, mFaceProps, wakefulnessLifecycle,
- userManager, lockPatternUtils, mInteractionJankMonitor);
+ userManager, lockPatternUtils, mInteractionJankMonitor,
+ mBiometricPromptInteractor, mCredentialViewModelProvider);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
deleted file mode 100644
index 76cd3f4..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
- * Copyright (C) 2019 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.biometrics;
-
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.view.WindowInsets.Type.ime;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.graphics.Insets;
-import android.os.UserHandle;
-import android.text.InputType;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.View.OnApplyWindowInsetsListener;
-import android.view.ViewGroup;
-import android.view.WindowInsets;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.ImeAwareEditText;
-import android.widget.TextView;
-
-import com.android.internal.widget.LockPatternChecker;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.LockscreenCredential;
-import com.android.internal.widget.VerifyCredentialResponse;
-import com.android.systemui.Dumpable;
-import com.android.systemui.R;
-
-import java.io.PrintWriter;
-
-/**
- * Pin and Password UI
- */
-public class AuthCredentialPasswordView extends AuthCredentialView
- implements TextView.OnEditorActionListener, OnApplyWindowInsetsListener, Dumpable {
-
- private static final String TAG = "BiometricPrompt/AuthCredentialPasswordView";
-
- private final InputMethodManager mImm;
- private ImeAwareEditText mPasswordField;
- private ViewGroup mAuthCredentialHeader;
- private ViewGroup mAuthCredentialInput;
- private int mBottomInset = 0;
-
- public AuthCredentialPasswordView(Context context,
- AttributeSet attrs) {
- super(context, attrs);
- mImm = mContext.getSystemService(InputMethodManager.class);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
-
- mAuthCredentialHeader = findViewById(R.id.auth_credential_header);
- mAuthCredentialInput = findViewById(R.id.auth_credential_input);
- mPasswordField = findViewById(R.id.lockPassword);
- mPasswordField.setOnEditorActionListener(this);
- // TODO: De-dupe the logic with AuthContainerView
- mPasswordField.setOnKeyListener((v, keyCode, event) -> {
- if (keyCode != KeyEvent.KEYCODE_BACK) {
- return false;
- }
- if (event.getAction() == KeyEvent.ACTION_UP) {
- mContainerView.sendEarlyUserCanceled();
- mContainerView.animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
- }
- return true;
- });
-
- setOnApplyWindowInsetsListener(this);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- mPasswordField.setTextOperationUser(UserHandle.of(mUserId));
- if (mCredentialType == Utils.CREDENTIAL_PIN) {
- mPasswordField.setInputType(
- InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
- }
-
- mPasswordField.requestFocus();
- mPasswordField.scheduleShowSoftInput();
- }
-
- @Override
- public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
- // Check if this was the result of hitting the enter key
- final boolean isSoftImeEvent = event == null
- && (actionId == EditorInfo.IME_NULL
- || actionId == EditorInfo.IME_ACTION_DONE
- || actionId == EditorInfo.IME_ACTION_NEXT);
- final boolean isKeyboardEnterKey = event != null
- && KeyEvent.isConfirmKey(event.getKeyCode())
- && event.getAction() == KeyEvent.ACTION_DOWN;
- if (isSoftImeEvent || isKeyboardEnterKey) {
- checkPasswordAndUnlock();
- return true;
- }
- return false;
- }
-
- private void checkPasswordAndUnlock() {
- try (LockscreenCredential password = mCredentialType == Utils.CREDENTIAL_PIN
- ? LockscreenCredential.createPinOrNone(mPasswordField.getText())
- : LockscreenCredential.createPasswordOrNone(mPasswordField.getText())) {
- if (password.isNone()) {
- return;
- }
-
- // Request LockSettingsService to return the Gatekeeper Password in the
- // VerifyCredentialResponse so that we can request a Gatekeeper HAT with the
- // Gatekeeper Password and operationId.
- mPendingLockCheck = LockPatternChecker.verifyCredential(mLockPatternUtils,
- password, mEffectiveUserId, LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE,
- this::onCredentialVerified);
- }
- }
-
- @Override
- protected void onCredentialVerified(@NonNull VerifyCredentialResponse response,
- int timeoutMs) {
- super.onCredentialVerified(response, timeoutMs);
-
- if (response.isMatched()) {
- mImm.hideSoftInputFromWindow(getWindowToken(), 0 /* flags */);
- } else {
- mPasswordField.setText("");
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
-
- if (mAuthCredentialInput == null || mAuthCredentialHeader == null || mSubtitleView == null
- || mDescriptionView == null || mPasswordField == null || mErrorView == null) {
- return;
- }
-
- int inputLeftBound;
- int inputTopBound;
- int headerRightBound = right;
- int headerTopBounds = top;
- final int subTitleBottom = (mSubtitleView.getVisibility() == GONE) ? mTitleView.getBottom()
- : mSubtitleView.getBottom();
- final int descBottom = (mDescriptionView.getVisibility() == GONE) ? subTitleBottom
- : mDescriptionView.getBottom();
- if (getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE) {
- inputTopBound = (bottom - mAuthCredentialInput.getHeight()) / 2;
- inputLeftBound = (right - left) / 2;
- headerRightBound = inputLeftBound;
- headerTopBounds -= Math.min(mIconView.getBottom(), mBottomInset);
- } else {
- inputTopBound =
- descBottom + (bottom - descBottom - mAuthCredentialInput.getHeight()) / 2;
- inputLeftBound = (right - left - mAuthCredentialInput.getWidth()) / 2;
- }
-
- if (mDescriptionView.getBottom() > mBottomInset) {
- mAuthCredentialHeader.layout(left, headerTopBounds, headerRightBound, bottom);
- }
- mAuthCredentialInput.layout(inputLeftBound, inputTopBound, right, bottom);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- final int newWidth = MeasureSpec.getSize(widthMeasureSpec);
- final int newHeight = MeasureSpec.getSize(heightMeasureSpec) - mBottomInset;
-
- setMeasuredDimension(newWidth, newHeight);
-
- final int halfWidthSpec = MeasureSpec.makeMeasureSpec(getWidth() / 2,
- MeasureSpec.AT_MOST);
- final int fullHeightSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.UNSPECIFIED);
- if (getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE) {
- measureChildren(halfWidthSpec, fullHeightSpec);
- } else {
- measureChildren(widthMeasureSpec, fullHeightSpec);
- }
- }
-
- @NonNull
- @Override
- public WindowInsets onApplyWindowInsets(@NonNull View v, WindowInsets insets) {
-
- final Insets bottomInset = insets.getInsets(ime());
- if (v instanceof AuthCredentialPasswordView && mBottomInset != bottomInset.bottom) {
- mBottomInset = bottomInset.bottom;
- if (mBottomInset > 0
- && getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE) {
- mTitleView.setSingleLine(true);
- mTitleView.setEllipsize(TextUtils.TruncateAt.MARQUEE);
- mTitleView.setMarqueeRepeatLimit(-1);
- // select to enable marquee unless a screen reader is enabled
- mTitleView.setSelected(!mAccessibilityManager.isEnabled()
- || !mAccessibilityManager.isTouchExplorationEnabled());
- } else {
- mTitleView.setSingleLine(false);
- mTitleView.setEllipsize(null);
- // select to enable marquee unless a screen reader is enabled
- mTitleView.setSelected(false);
- }
- requestLayout();
- }
- return insets;
- }
-
- @Override
- public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
- pw.println(TAG + "State:");
- pw.println(" mBottomInset=" + mBottomInset);
- pw.println(" mAuthCredentialHeader size=(" + mAuthCredentialHeader.getWidth() + ","
- + mAuthCredentialHeader.getHeight());
- pw.println(" mAuthCredentialInput size=(" + mAuthCredentialInput.getWidth() + ","
- + mAuthCredentialInput.getHeight());
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
deleted file mode 100644
index f9e44a0..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2019 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.biometrics;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.util.AttributeSet;
-
-import com.android.internal.widget.LockPatternChecker;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.LockPatternView;
-import com.android.internal.widget.LockscreenCredential;
-import com.android.internal.widget.VerifyCredentialResponse;
-import com.android.systemui.R;
-
-import java.util.List;
-
-/**
- * Pattern UI
- */
-public class AuthCredentialPatternView extends AuthCredentialView {
-
- private LockPatternView mLockPatternView;
-
- private class UnlockPatternListener implements LockPatternView.OnPatternListener {
-
- @Override
- public void onPatternStart() {
-
- }
-
- @Override
- public void onPatternCleared() {
-
- }
-
- @Override
- public void onPatternCellAdded(List<LockPatternView.Cell> pattern) {
-
- }
-
- @Override
- public void onPatternDetected(List<LockPatternView.Cell> pattern) {
- if (mPendingLockCheck != null) {
- mPendingLockCheck.cancel(false);
- }
-
- mLockPatternView.setEnabled(false);
-
- if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
- // Pattern size is less than the minimum, do not count it as a failed attempt.
- onPatternVerified(VerifyCredentialResponse.ERROR, 0 /* timeoutMs */);
- return;
- }
-
- try (LockscreenCredential credential = LockscreenCredential.createPattern(pattern)) {
- // Request LockSettingsService to return the Gatekeeper Password in the
- // VerifyCredentialResponse so that we can request a Gatekeeper HAT with the
- // Gatekeeper Password and operationId.
- mPendingLockCheck = LockPatternChecker.verifyCredential(
- mLockPatternUtils,
- credential,
- mEffectiveUserId,
- LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE,
- this::onPatternVerified);
- }
- }
-
- private void onPatternVerified(@NonNull VerifyCredentialResponse response, int timeoutMs) {
- AuthCredentialPatternView.this.onCredentialVerified(response, timeoutMs);
- if (timeoutMs > 0) {
- mLockPatternView.setEnabled(false);
- } else {
- mLockPatternView.setEnabled(true);
- }
- }
- }
-
- @Override
- protected void onErrorTimeoutFinish() {
- super.onErrorTimeoutFinish();
- // select to enable marquee unless a screen reader is enabled
- mLockPatternView.setEnabled(!mAccessibilityManager.isEnabled()
- || !mAccessibilityManager.isTouchExplorationEnabled());
- }
-
- public AuthCredentialPatternView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mLockPatternView = findViewById(R.id.lockPattern);
- mLockPatternView.setOnPatternListener(new UnlockPatternListener());
- mLockPatternView.setInStealthMode(
- !mLockPatternUtils.isVisiblePatternEnabled(mUserId));
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
deleted file mode 100644
index fa623d1..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
+++ /dev/null
@@ -1,565 +0,0 @@
-/*
- * Copyright (C) 2019 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.biometrics;
-
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT;
-import static android.app.admin.DevicePolicyResources.UNDEFINED;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.AlertDialog;
-import android.app.admin.DevicePolicyManager;
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.graphics.drawable.Drawable;
-import android.hardware.biometrics.BiometricPrompt;
-import android.hardware.biometrics.PromptInfo;
-import android.os.AsyncTask;
-import android.os.CountDownTimer;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.SystemClock;
-import android.os.UserManager;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.annotation.StringRes;
-
-import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.VerifyCredentialResponse;
-import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Abstract base class for Pin, Pattern, or Password authentication, for
- * {@link BiometricPrompt.Builder#setAllowedAuthenticators(int)}}
- */
-public abstract class AuthCredentialView extends LinearLayout {
- private static final String TAG = "BiometricPrompt/AuthCredentialView";
- private static final int ERROR_DURATION_MS = 3000;
-
- static final int USER_TYPE_PRIMARY = 1;
- static final int USER_TYPE_MANAGED_PROFILE = 2;
- static final int USER_TYPE_SECONDARY = 3;
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({USER_TYPE_PRIMARY, USER_TYPE_MANAGED_PROFILE, USER_TYPE_SECONDARY})
- private @interface UserType {}
-
- protected final Handler mHandler;
- protected final LockPatternUtils mLockPatternUtils;
-
- protected final AccessibilityManager mAccessibilityManager;
- private final UserManager mUserManager;
- private final DevicePolicyManager mDevicePolicyManager;
-
- private PromptInfo mPromptInfo;
- private AuthPanelController mPanelController;
- private boolean mShouldAnimatePanel;
- private boolean mShouldAnimateContents;
-
- protected TextView mTitleView;
- protected TextView mSubtitleView;
- protected TextView mDescriptionView;
- protected ImageView mIconView;
- protected TextView mErrorView;
-
- protected @Utils.CredentialType int mCredentialType;
- protected AuthContainerView mContainerView;
- protected Callback mCallback;
- protected AsyncTask<?, ?, ?> mPendingLockCheck;
- protected int mUserId;
- protected long mOperationId;
- protected int mEffectiveUserId;
- protected ErrorTimer mErrorTimer;
-
- protected @Background DelayableExecutor mBackgroundExecutor;
-
- interface Callback {
- void onCredentialMatched(byte[] attestation);
- }
-
- protected static class ErrorTimer extends CountDownTimer {
- private final TextView mErrorView;
- private final Context mContext;
-
- /**
- * @param millisInFuture The number of millis in the future from the call
- * to {@link #start()} until the countdown is done and {@link
- * #onFinish()}
- * is called.
- * @param countDownInterval The interval along the way to receive
- * {@link #onTick(long)} callbacks.
- */
- public ErrorTimer(Context context, long millisInFuture, long countDownInterval,
- TextView errorView) {
- super(millisInFuture, countDownInterval);
- mErrorView = errorView;
- mContext = context;
- }
-
- @Override
- public void onTick(long millisUntilFinished) {
- final int secondsCountdown = (int) (millisUntilFinished / 1000);
- mErrorView.setText(mContext.getString(
- R.string.biometric_dialog_credential_too_many_attempts, secondsCountdown));
- }
-
- @Override
- public void onFinish() {
- if (mErrorView != null) {
- mErrorView.setText("");
- }
- }
- }
-
- protected final Runnable mClearErrorRunnable = new Runnable() {
- @Override
- public void run() {
- if (mErrorView != null) {
- mErrorView.setText("");
- }
- }
- };
-
- public AuthCredentialView(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- mLockPatternUtils = new LockPatternUtils(mContext);
- mHandler = new Handler(Looper.getMainLooper());
- mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
- mUserManager = mContext.getSystemService(UserManager.class);
- mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
- }
-
- protected void showError(String error) {
- if (mHandler != null) {
- mHandler.removeCallbacks(mClearErrorRunnable);
- mHandler.postDelayed(mClearErrorRunnable, ERROR_DURATION_MS);
- }
- if (mErrorView != null) {
- mErrorView.setText(error);
- }
- }
-
- private void setTextOrHide(TextView view, CharSequence text) {
- if (TextUtils.isEmpty(text)) {
- view.setVisibility(View.GONE);
- } else {
- view.setText(text);
- }
-
- Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
- }
-
- private void setText(TextView view, CharSequence text) {
- view.setText(text);
- }
-
- void setUserId(int userId) {
- mUserId = userId;
- }
-
- void setOperationId(long operationId) {
- mOperationId = operationId;
- }
-
- void setEffectiveUserId(int effectiveUserId) {
- mEffectiveUserId = effectiveUserId;
- }
-
- void setCredentialType(@Utils.CredentialType int credentialType) {
- mCredentialType = credentialType;
- }
-
- void setCallback(Callback callback) {
- mCallback = callback;
- }
-
- void setPromptInfo(PromptInfo promptInfo) {
- mPromptInfo = promptInfo;
- }
-
- void setPanelController(AuthPanelController panelController, boolean animatePanel) {
- mPanelController = panelController;
- mShouldAnimatePanel = animatePanel;
- }
-
- void setShouldAnimateContents(boolean animateContents) {
- mShouldAnimateContents = animateContents;
- }
-
- void setContainerView(AuthContainerView containerView) {
- mContainerView = containerView;
- }
-
- void setBackgroundExecutor(@Background DelayableExecutor bgExecutor) {
- mBackgroundExecutor = bgExecutor;
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- final CharSequence title = getTitle(mPromptInfo);
- setText(mTitleView, title);
- setTextOrHide(mSubtitleView, getSubtitle(mPromptInfo));
- setTextOrHide(mDescriptionView, getDescription(mPromptInfo));
- announceForAccessibility(title);
-
- if (mIconView != null) {
- final boolean isManagedProfile = Utils.isManagedProfile(mContext, mEffectiveUserId);
- final Drawable image;
- if (isManagedProfile) {
- image = getResources().getDrawable(R.drawable.auth_dialog_enterprise,
- mContext.getTheme());
- } else {
- image = getResources().getDrawable(R.drawable.auth_dialog_lock,
- mContext.getTheme());
- }
- mIconView.setImageDrawable(image);
- }
-
- // Only animate this if we're transitioning from a biometric view.
- if (mShouldAnimateContents) {
- setTranslationY(getResources()
- .getDimension(R.dimen.biometric_dialog_credential_translation_offset));
- setAlpha(0);
-
- postOnAnimation(() -> {
- animate().translationY(0)
- .setDuration(AuthDialog.ANIMATE_CREDENTIAL_INITIAL_DURATION_MS)
- .alpha(1.f)
- .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
- .withLayer()
- .start();
- });
- }
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- if (mErrorTimer != null) {
- mErrorTimer.cancel();
- }
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mTitleView = findViewById(R.id.title);
- mSubtitleView = findViewById(R.id.subtitle);
- mDescriptionView = findViewById(R.id.description);
- mIconView = findViewById(R.id.icon);
- mErrorView = findViewById(R.id.error);
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
-
- if (mShouldAnimatePanel) {
- // Credential view is always full screen.
- mPanelController.setUseFullScreen(true);
- mPanelController.updateForContentDimensions(mPanelController.getContainerWidth(),
- mPanelController.getContainerHeight(), 0 /* animateDurationMs */);
- mShouldAnimatePanel = false;
- }
- }
-
- protected void onErrorTimeoutFinish() {}
-
- protected void onCredentialVerified(@NonNull VerifyCredentialResponse response, int timeoutMs) {
- if (response.isMatched()) {
- mClearErrorRunnable.run();
- mLockPatternUtils.userPresent(mEffectiveUserId);
-
- // The response passed into this method contains the Gatekeeper Password. We still
- // have to request Gatekeeper to create a Hardware Auth Token with the
- // Gatekeeper Password and Challenge (keystore operationId in this case)
- final long pwHandle = response.getGatekeeperPasswordHandle();
- final VerifyCredentialResponse gkResponse = mLockPatternUtils
- .verifyGatekeeperPasswordHandle(pwHandle, mOperationId, mEffectiveUserId);
-
- mCallback.onCredentialMatched(gkResponse.getGatekeeperHAT());
- mLockPatternUtils.removeGatekeeperPasswordHandle(pwHandle);
- } else {
- if (timeoutMs > 0) {
- mHandler.removeCallbacks(mClearErrorRunnable);
- long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
- mEffectiveUserId, timeoutMs);
- mErrorTimer = new ErrorTimer(mContext,
- deadline - SystemClock.elapsedRealtime(),
- LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS,
- mErrorView) {
- @Override
- public void onFinish() {
- onErrorTimeoutFinish();
- mClearErrorRunnable.run();
- }
- };
- mErrorTimer.start();
- } else {
- final boolean didUpdateErrorText = reportFailedAttempt();
- if (!didUpdateErrorText) {
- final @StringRes int errorRes;
- switch (mCredentialType) {
- case Utils.CREDENTIAL_PIN:
- errorRes = R.string.biometric_dialog_wrong_pin;
- break;
- case Utils.CREDENTIAL_PATTERN:
- errorRes = R.string.biometric_dialog_wrong_pattern;
- break;
- case Utils.CREDENTIAL_PASSWORD:
- default:
- errorRes = R.string.biometric_dialog_wrong_password;
- break;
- }
- showError(getResources().getString(errorRes));
- }
- }
- }
- }
-
- private boolean reportFailedAttempt() {
- boolean result = updateErrorMessage(
- mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId) + 1);
- mLockPatternUtils.reportFailedPasswordAttempt(mEffectiveUserId);
- return result;
- }
-
- private boolean updateErrorMessage(int numAttempts) {
- // Don't show any message if there's no maximum number of attempts.
- final int maxAttempts = mLockPatternUtils.getMaximumFailedPasswordsForWipe(
- mEffectiveUserId);
- if (maxAttempts <= 0 || numAttempts <= 0) {
- return false;
- }
-
- // Update the on-screen error string.
- if (mErrorView != null) {
- final String message = getResources().getString(
- R.string.biometric_dialog_credential_attempts_before_wipe,
- numAttempts,
- maxAttempts);
- showError(message);
- }
-
- // Only show dialog if <=1 attempts are left before wiping.
- final int remainingAttempts = maxAttempts - numAttempts;
- if (remainingAttempts == 1) {
- showLastAttemptBeforeWipeDialog();
- } else if (remainingAttempts <= 0) {
- showNowWipingDialog();
- }
- return true;
- }
-
- private void showLastAttemptBeforeWipeDialog() {
- mBackgroundExecutor.execute(() -> {
- final AlertDialog alertDialog = new AlertDialog.Builder(mContext)
- .setTitle(R.string.biometric_dialog_last_attempt_before_wipe_dialog_title)
- .setMessage(
- getLastAttemptBeforeWipeMessage(getUserTypeForWipe(), mCredentialType))
- .setPositiveButton(android.R.string.ok, null)
- .create();
- alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
- mHandler.post(alertDialog::show);
- });
- }
-
- private void showNowWipingDialog() {
- mBackgroundExecutor.execute(() -> {
- String nowWipingMessage = getNowWipingMessage(getUserTypeForWipe());
- final AlertDialog alertDialog = new AlertDialog.Builder(mContext)
- .setMessage(nowWipingMessage)
- .setPositiveButton(
- com.android.settingslib.R.string.failed_attempts_now_wiping_dialog_dismiss,
- null /* OnClickListener */)
- .setOnDismissListener(
- dialog -> mContainerView.animateAway(
- AuthDialogCallback.DISMISSED_ERROR))
- .create();
- alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
- mHandler.post(alertDialog::show);
- });
- }
-
- private @UserType int getUserTypeForWipe() {
- final UserInfo userToBeWiped = mUserManager.getUserInfo(
- mDevicePolicyManager.getProfileWithMinimumFailedPasswordsForWipe(mEffectiveUserId));
- if (userToBeWiped == null || userToBeWiped.isPrimary()) {
- return USER_TYPE_PRIMARY;
- } else if (userToBeWiped.isManagedProfile()) {
- return USER_TYPE_MANAGED_PROFILE;
- } else {
- return USER_TYPE_SECONDARY;
- }
- }
-
- // This should not be called on the main thread to avoid making an IPC.
- private String getLastAttemptBeforeWipeMessage(
- @UserType int userType, @Utils.CredentialType int credentialType) {
- switch (userType) {
- case USER_TYPE_PRIMARY:
- return getLastAttemptBeforeWipeDeviceMessage(credentialType);
- case USER_TYPE_MANAGED_PROFILE:
- return getLastAttemptBeforeWipeProfileMessage(credentialType);
- case USER_TYPE_SECONDARY:
- return getLastAttemptBeforeWipeUserMessage(credentialType);
- default:
- throw new IllegalArgumentException("Unrecognized user type:" + userType);
- }
- }
-
- private String getLastAttemptBeforeWipeDeviceMessage(
- @Utils.CredentialType int credentialType) {
- switch (credentialType) {
- case Utils.CREDENTIAL_PIN:
- return mContext.getString(
- R.string.biometric_dialog_last_pin_attempt_before_wipe_device);
- case Utils.CREDENTIAL_PATTERN:
- return mContext.getString(
- R.string.biometric_dialog_last_pattern_attempt_before_wipe_device);
- case Utils.CREDENTIAL_PASSWORD:
- default:
- return mContext.getString(
- R.string.biometric_dialog_last_password_attempt_before_wipe_device);
- }
- }
-
- // This should not be called on the main thread to avoid making an IPC.
- private String getLastAttemptBeforeWipeProfileMessage(
- @Utils.CredentialType int credentialType) {
- return mDevicePolicyManager.getResources().getString(
- getLastAttemptBeforeWipeProfileUpdatableStringId(credentialType),
- () -> getLastAttemptBeforeWipeProfileDefaultMessage(credentialType));
- }
-
- private static String getLastAttemptBeforeWipeProfileUpdatableStringId(
- @Utils.CredentialType int credentialType) {
- switch (credentialType) {
- case Utils.CREDENTIAL_PIN:
- return BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT;
- case Utils.CREDENTIAL_PATTERN:
- return BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT;
- case Utils.CREDENTIAL_PASSWORD:
- default:
- return BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT;
- }
- }
-
- private String getLastAttemptBeforeWipeProfileDefaultMessage(
- @Utils.CredentialType int credentialType) {
- int resId;
- switch (credentialType) {
- case Utils.CREDENTIAL_PIN:
- resId = R.string.biometric_dialog_last_pin_attempt_before_wipe_profile;
- break;
- case Utils.CREDENTIAL_PATTERN:
- resId = R.string.biometric_dialog_last_pattern_attempt_before_wipe_profile;
- break;
- case Utils.CREDENTIAL_PASSWORD:
- default:
- resId = R.string.biometric_dialog_last_password_attempt_before_wipe_profile;
- }
- return mContext.getString(resId);
- }
-
- private String getLastAttemptBeforeWipeUserMessage(
- @Utils.CredentialType int credentialType) {
- int resId;
- switch (credentialType) {
- case Utils.CREDENTIAL_PIN:
- resId = R.string.biometric_dialog_last_pin_attempt_before_wipe_user;
- break;
- case Utils.CREDENTIAL_PATTERN:
- resId = R.string.biometric_dialog_last_pattern_attempt_before_wipe_user;
- break;
- case Utils.CREDENTIAL_PASSWORD:
- default:
- resId = R.string.biometric_dialog_last_password_attempt_before_wipe_user;
- }
- return mContext.getString(resId);
- }
-
- private String getNowWipingMessage(@UserType int userType) {
- return mDevicePolicyManager.getResources().getString(
- getNowWipingUpdatableStringId(userType),
- () -> getNowWipingDefaultMessage(userType));
- }
-
- private String getNowWipingUpdatableStringId(@UserType int userType) {
- switch (userType) {
- case USER_TYPE_MANAGED_PROFILE:
- return BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS;
- default:
- return UNDEFINED;
- }
- }
-
- private String getNowWipingDefaultMessage(@UserType int userType) {
- int resId;
- switch (userType) {
- case USER_TYPE_PRIMARY:
- resId = com.android.settingslib.R.string.failed_attempts_now_wiping_device;
- break;
- case USER_TYPE_MANAGED_PROFILE:
- resId = com.android.settingslib.R.string.failed_attempts_now_wiping_profile;
- break;
- case USER_TYPE_SECONDARY:
- resId = com.android.settingslib.R.string.failed_attempts_now_wiping_user;
- break;
- default:
- throw new IllegalArgumentException("Unrecognized user type:" + userType);
- }
- return mContext.getString(resId);
- }
-
- @Nullable
- private static CharSequence getTitle(@NonNull PromptInfo promptInfo) {
- final CharSequence credentialTitle = promptInfo.getDeviceCredentialTitle();
- return credentialTitle != null ? credentialTitle : promptInfo.getTitle();
- }
-
- @Nullable
- private static CharSequence getSubtitle(@NonNull PromptInfo promptInfo) {
- final CharSequence credentialSubtitle = promptInfo.getDeviceCredentialSubtitle();
- return credentialSubtitle != null ? credentialSubtitle : promptInfo.getSubtitle();
- }
-
- @Nullable
- private static CharSequence getDescription(@NonNull PromptInfo promptInfo) {
- final CharSequence credentialDescription = promptInfo.getDeviceCredentialDescription();
- return credentialDescription != null ? credentialDescription : promptInfo.getDescription();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
index f1e42e0..5c616f0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
@@ -177,11 +177,11 @@
}
}
- int getContainerWidth() {
+ public int getContainerWidth() {
return mContainerWidth;
}
- int getContainerHeight() {
+ public int getContainerHeight() {
return mContainerHeight;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 3273d74..48c60a0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -60,6 +60,8 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -119,6 +121,7 @@
@NonNull private final SystemUIDialogManager mDialogManager;
@NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@NonNull private final VibratorHelper mVibrator;
+ @NonNull private final FeatureFlags mFeatureFlags;
@NonNull private final FalsingManager mFalsingManager;
@NonNull private final PowerManager mPowerManager;
@NonNull private final AccessibilityManager mAccessibilityManager;
@@ -202,6 +205,10 @@
@Override
public void showUdfpsOverlay(long requestId, int sensorId, int reason,
@NonNull IUdfpsOverlayControllerCallback callback) {
+ if (mFeatureFlags.isEnabled(Flags.NEW_UDFPS_OVERLAY)) {
+ return;
+ }
+
mFgExecutor.execute(() -> UdfpsController.this.showUdfpsOverlay(
new UdfpsControllerOverlay(mContext, mFingerprintManager, mInflater,
mWindowManager, mAccessibilityManager, mStatusBarStateController,
@@ -217,6 +224,10 @@
@Override
public void hideUdfpsOverlay(int sensorId) {
+ if (mFeatureFlags.isEnabled(Flags.NEW_UDFPS_OVERLAY)) {
+ return;
+ }
+
mFgExecutor.execute(() -> {
if (mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
// if we get here, we expect keyguardUpdateMonitor's fingerprintRunningState
@@ -590,6 +601,7 @@
@NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@NonNull DumpManager dumpManager,
@NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
+ @NonNull FeatureFlags featureFlags,
@NonNull FalsingManager falsingManager,
@NonNull PowerManager powerManager,
@NonNull AccessibilityManager accessibilityManager,
@@ -625,6 +637,7 @@
mDumpManager = dumpManager;
mDialogManager = dialogManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mFeatureFlags = featureFlags;
mFalsingManager = falsingManager;
mPowerManager = powerManager;
mAccessibilityManager = accessibilityManager;
@@ -859,9 +872,7 @@
playStartHaptic();
if (!mKeyguardUpdateMonitor.isFaceDetectionRunning()) {
- mKeyguardUpdateMonitor.requestFaceAuth(
- /* userInitiatedRequest */ false,
- FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
+ mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
}
}
mOnFingerDown = true;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt
index 6e78f3d..142642a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt
@@ -19,26 +19,40 @@
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.PixelFormat
+import android.graphics.Point
import android.graphics.Rect
+import android.hardware.biometrics.BiometricOverlayConstants
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
+import android.hardware.fingerprint.IUdfpsOverlay
import android.os.Handler
+import android.provider.Settings
import android.view.MotionEvent
-import android.view.View
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.Execution
-import java.util.*
+import java.util.Optional
import java.util.concurrent.Executor
import javax.inject.Inject
+import kotlin.math.cos
+import kotlin.math.pow
+import kotlin.math.sin
private const val TAG = "UdfpsOverlay"
+const val SETTING_OVERLAY_DEBUG = "udfps_overlay_debug"
+
+// Number of sensor points needed inside ellipse for good overlap
+private const val NEEDED_POINTS = 2
+
@SuppressLint("ClickableViewAccessibility")
@SysUISingleton
class UdfpsOverlay
@@ -51,10 +65,11 @@
private val handler: Handler,
private val biometricExecutor: Executor,
private val alternateTouchProvider: Optional<AlternateUdfpsTouchProvider>,
- private val fgExecutor: DelayableExecutor,
+ @Main private val fgExecutor: DelayableExecutor,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val authController: AuthController,
- private val udfpsLogger: UdfpsLogger
+ private val udfpsLogger: UdfpsLogger,
+ private var featureFlags: FeatureFlags
) : CoreStartable {
/** The view, when [isShowing], or null. */
@@ -64,7 +79,11 @@
private var requestId: Long = 0
private var onFingerDown = false
val size = windowManager.maximumWindowMetrics.bounds
+
val udfpsProps: MutableList<FingerprintSensorPropertiesInternal> = mutableListOf()
+ var points: Array<Point> = emptyArray()
+ var processedMotionEvent = false
+ var isShowing = false
private var params: UdfpsOverlayParams = UdfpsOverlayParams()
@@ -87,41 +106,140 @@
inputFeatures = INPUT_FEATURE_SPY
}
- fun onTouch(v: View, event: MotionEvent): Boolean {
- val view = v as UdfpsOverlayView
+ fun onTouch(event: MotionEvent): Boolean {
+ val view = overlayView!!
return when (event.action) {
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_MOVE -> {
onFingerDown = true
if (!view.isDisplayConfigured && alternateTouchProvider.isPresent) {
- biometricExecutor.execute {
- alternateTouchProvider
- .get()
- .onPointerDown(
- requestId,
- event.x.toInt(),
- event.y.toInt(),
- event.touchMinor,
- event.touchMajor
- )
- }
- fgExecutor.execute {
- if (keyguardUpdateMonitor.isFingerprintDetectionRunning) {
- keyguardUpdateMonitor.onUdfpsPointerDown(requestId.toInt())
+ view.processMotionEvent(event)
+
+ val goodOverlap =
+ if (featureFlags.isEnabled(Flags.NEW_ELLIPSE_DETECTION)) {
+ isGoodEllipseOverlap(event)
+ } else {
+ isGoodCentroidOverlap(event)
+ }
+
+ if (!processedMotionEvent && goodOverlap) {
+ biometricExecutor.execute {
+ alternateTouchProvider
+ .get()
+ .onPointerDown(
+ requestId,
+ event.rawX.toInt(),
+ event.rawY.toInt(),
+ event.touchMinor,
+ event.touchMajor
+ )
+ }
+ fgExecutor.execute {
+ if (keyguardUpdateMonitor.isFingerprintDetectionRunning) {
+ keyguardUpdateMonitor.onUdfpsPointerDown(requestId.toInt())
+ }
+
+ view.configureDisplay {
+ biometricExecutor.execute {
+ alternateTouchProvider.get().onUiReady()
+ }
+ }
+
+ processedMotionEvent = true
}
}
- view.configureDisplay {
- biometricExecutor.execute { alternateTouchProvider.get().onUiReady() }
- }
+ view.invalidate()
}
-
true
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_CANCEL -> {
- if (onFingerDown && alternateTouchProvider.isPresent) {
+ if (processedMotionEvent && alternateTouchProvider.isPresent) {
+ biometricExecutor.execute {
+ alternateTouchProvider.get().onPointerUp(requestId)
+ }
+ fgExecutor.execute {
+ if (keyguardUpdateMonitor.isFingerprintDetectionRunning) {
+ keyguardUpdateMonitor.onUdfpsPointerUp(requestId.toInt())
+ }
+ }
+
+ processedMotionEvent = false
+ }
+
+ if (view.isDisplayConfigured) {
+ view.unconfigureDisplay()
+ }
+
+ view.invalidate()
+ true
+ }
+ else -> false
+ }
+ }
+
+ fun isGoodEllipseOverlap(event: MotionEvent): Boolean {
+ return points.count { checkPoint(event, it) } >= NEEDED_POINTS
+ }
+
+ fun isGoodCentroidOverlap(event: MotionEvent): Boolean {
+ return params.sensorBounds.contains(event.rawX.toInt(), event.rawY.toInt())
+ }
+
+ fun checkPoint(event: MotionEvent, point: Point): Boolean {
+ // Calculate if sensor point is within ellipse
+ // Formula: ((cos(o)(xE - xS) + sin(o)(yE - yS))^2 / a^2) + ((sin(o)(xE - xS) + cos(o)(yE -
+ // yS))^2 / b^2) <= 1
+ val a: Float = cos(event.orientation) * (point.x - event.rawX)
+ val b: Float = sin(event.orientation) * (point.y - event.rawY)
+ val c: Float = sin(event.orientation) * (point.x - event.rawX)
+ val d: Float = cos(event.orientation) * (point.y - event.rawY)
+ val result =
+ (a + b).pow(2) / (event.touchMinor / 2).pow(2) +
+ (c - d).pow(2) / (event.touchMajor / 2).pow(2)
+
+ return result <= 1
+ }
+
+ fun show(requestId: Long) {
+ if (!featureFlags.isEnabled(Flags.NEW_UDFPS_OVERLAY)) {
+ return
+ }
+
+ this.requestId = requestId
+ fgExecutor.execute {
+ if (overlayView == null && alternateTouchProvider.isPresent) {
+ UdfpsOverlayView(context, null).let {
+ it.overlayParams = params
+ it.setUdfpsDisplayMode(
+ UdfpsDisplayMode(context, execution, authController, udfpsLogger)
+ )
+ it.setOnTouchListener { _, event -> onTouch(event) }
+ it.sensorPoints = points
+ it.debugOverlay =
+ Settings.Global.getInt(
+ context.contentResolver,
+ SETTING_OVERLAY_DEBUG,
+ 0 /* def */
+ ) != 0
+ overlayView = it
+ }
+ windowManager.addView(overlayView, coreLayoutParams)
+ isShowing = true
+ }
+ }
+ }
+
+ fun hide() {
+ if (!featureFlags.isEnabled(Flags.NEW_UDFPS_OVERLAY)) {
+ return
+ }
+
+ fgExecutor.execute {
+ if (overlayView != null && isShowing && alternateTouchProvider.isPresent) {
+ if (processedMotionEvent) {
biometricExecutor.execute {
alternateTouchProvider.get().onPointerUp(requestId)
}
@@ -131,44 +249,23 @@
}
}
}
- onFingerDown = false
- if (view.isDisplayConfigured) {
- view.unconfigureDisplay()
+
+ if (overlayView!!.isDisplayConfigured) {
+ overlayView!!.unconfigureDisplay()
}
- true
+ overlayView?.apply {
+ windowManager.removeView(this)
+ setOnTouchListener(null)
+ }
+
+ isShowing = false
+ overlayView = null
+ processedMotionEvent = false
}
- else -> false
}
}
- fun show(requestId: Long): Boolean {
- this.requestId = requestId
- if (overlayView == null && alternateTouchProvider.isPresent) {
- UdfpsOverlayView(context, null).let {
- it.overlayParams = params
- it.setUdfpsDisplayMode(
- UdfpsDisplayMode(context, execution, authController, udfpsLogger)
- )
- it.setOnTouchListener { v, event -> onTouch(v, event) }
- overlayView = it
- }
- windowManager.addView(overlayView, coreLayoutParams)
- return true
- }
-
- return false
- }
-
- fun hide() {
- overlayView?.apply {
- windowManager.removeView(this)
- setOnTouchListener(null)
- }
-
- overlayView = null
- }
-
@Override
override fun start() {
fingerprintManager?.addAuthenticatorsRegisteredCallback(
@@ -180,6 +277,18 @@
}
}
)
+
+ fingerprintManager?.setUdfpsOverlay(
+ object : IUdfpsOverlay.Stub() {
+ override fun show(
+ requestId: Long,
+ sensorId: Int,
+ @BiometricOverlayConstants.ShowReason reason: Int
+ ) = show(requestId)
+
+ override fun hide(sensorId: Int) = hide()
+ }
+ )
}
private fun handleAllFingerprintAuthenticatorsRegistered(
@@ -201,6 +310,24 @@
naturalDisplayHeight = size.height(),
scaleFactor = 1f
)
+
+ val sensorX = params.sensorBounds.centerX()
+ val sensorY = params.sensorBounds.centerY()
+ val cornerOffset: Int = params.sensorBounds.width() / 4
+ val sideOffset: Int = params.sensorBounds.width() / 3
+
+ points =
+ arrayOf(
+ Point(sensorX - cornerOffset, sensorY - cornerOffset),
+ Point(sensorX, sensorY - sideOffset),
+ Point(sensorX + cornerOffset, sensorY - cornerOffset),
+ Point(sensorX - sideOffset, sensorY),
+ Point(sensorX, sensorY),
+ Point(sensorX + sideOffset, sensorY),
+ Point(sensorX - cornerOffset, sensorY + cornerOffset),
+ Point(sensorX, sensorY + sideOffset),
+ Point(sensorX + cornerOffset, sensorY + cornerOffset)
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayView.kt
index d371332..4e6a06b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayView.kt
@@ -20,26 +20,41 @@
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
+import android.graphics.Point
import android.graphics.RectF
import android.util.AttributeSet
+import android.view.MotionEvent
import android.widget.FrameLayout
private const val TAG = "UdfpsOverlayView"
+private const val POINT_SIZE = 10f
class UdfpsOverlayView(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) {
-
- private val sensorRect = RectF()
var overlayParams = UdfpsOverlayParams()
private var mUdfpsDisplayMode: UdfpsDisplayMode? = null
+ var debugOverlay = false
+
var overlayPaint = Paint()
var sensorPaint = Paint()
+ var touchPaint = Paint()
+ var pointPaint = Paint()
val centerPaint = Paint()
+ var oval = RectF()
+
/** True after the call to [configureDisplay] and before the call to [unconfigureDisplay]. */
var isDisplayConfigured: Boolean = false
private set
+ var touchX: Float = 0f
+ var touchY: Float = 0f
+ var touchMinor: Float = 0f
+ var touchMajor: Float = 0f
+ var touchOrientation: Double = 0.0
+
+ var sensorPoints: Array<Point>? = null
+
init {
this.setWillNotDraw(false)
}
@@ -47,24 +62,60 @@
override fun onAttachedToWindow() {
super.onAttachedToWindow()
- overlayPaint.color = Color.argb(120, 255, 0, 0)
+ overlayPaint.color = Color.argb(100, 255, 0, 0)
overlayPaint.style = Paint.Style.FILL
+ touchPaint.color = Color.argb(200, 255, 255, 255)
+ touchPaint.style = Paint.Style.FILL
+
sensorPaint.color = Color.argb(150, 134, 204, 255)
sensorPaint.style = Paint.Style.FILL
+
+ pointPaint.color = Color.WHITE
+ pointPaint.style = Paint.Style.FILL
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
- canvas.drawRect(overlayParams.overlayBounds, overlayPaint)
- canvas.drawRect(overlayParams.sensorBounds, sensorPaint)
+ if (debugOverlay) {
+ // Draw overlay and sensor bounds
+ canvas.drawRect(overlayParams.overlayBounds, overlayPaint)
+ canvas.drawRect(overlayParams.sensorBounds, sensorPaint)
+ }
+
+ // Draw sensor circle
canvas.drawCircle(
overlayParams.sensorBounds.exactCenterX(),
overlayParams.sensorBounds.exactCenterY(),
overlayParams.sensorBounds.width().toFloat() / 2,
centerPaint
)
+
+ if (debugOverlay) {
+ // Draw Points
+ sensorPoints?.forEach {
+ canvas.drawCircle(it.x.toFloat(), it.y.toFloat(), POINT_SIZE, pointPaint)
+ }
+
+ // Draw touch oval
+ canvas.save()
+ canvas.rotate(Math.toDegrees(touchOrientation).toFloat(), touchX, touchY)
+
+ oval.setEmpty()
+ oval.set(
+ touchX - touchMinor / 2,
+ touchY + touchMajor / 2,
+ touchX + touchMinor / 2,
+ touchY - touchMajor / 2
+ )
+
+ canvas.drawOval(oval, touchPaint)
+
+ // Draw center point
+ canvas.drawCircle(touchX, touchY, POINT_SIZE, centerPaint)
+ canvas.restore()
+ }
}
fun setUdfpsDisplayMode(udfpsDisplayMode: UdfpsDisplayMode?) {
@@ -80,4 +131,12 @@
isDisplayConfigured = false
mUdfpsDisplayMode?.disable(null /* onDisabled */)
}
+
+ fun processMotionEvent(event: MotionEvent) {
+ touchX = event.rawX
+ touchY = event.rawY
+ touchMinor = event.touchMinor
+ touchMajor = event.touchMajor
+ touchOrientation = event.orientation.toDouble()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
index 75640b7..da50f1c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
@@ -62,7 +62,6 @@
if (args.size == 1 && args[0] == "hide") {
hideOverlay()
} else if (args.size == 2 && args[0] == "udfpsOverlay" && args[1] == "show") {
- hideOverlay()
showUdfpsOverlay()
} else if (args.size == 2 && args[0] == "udfpsOverlay" && args[1] == "hide") {
hideUdfpsOverlay()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
index b5d81f2..7c0c3b7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -16,32 +16,45 @@
package com.android.systemui.biometrics.dagger
+import com.android.systemui.biometrics.data.repository.PromptRepository
+import com.android.systemui.biometrics.data.repository.PromptRepositoryImpl
+import com.android.systemui.biometrics.domain.interactor.CredentialInteractor
+import com.android.systemui.biometrics.domain.interactor.CredentialInteractorImpl
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.util.concurrency.ThreadFactory
+import dagger.Binds
import dagger.Module
import dagger.Provides
import java.util.concurrent.Executor
import javax.inject.Qualifier
-/**
- * Dagger module for all things biometric.
- */
+/** Dagger module for all things biometric. */
@Module
-object BiometricsModule {
+interface BiometricsModule {
- /** Background [Executor] for HAL related operations. */
- @Provides
+ @Binds
@SysUISingleton
- @JvmStatic
- @BiometricsBackground
- fun providesPluginExecutor(threadFactory: ThreadFactory): Executor =
- threadFactory.buildExecutorOnNewThread("biometrics")
+ fun biometricPromptRepository(impl: PromptRepositoryImpl): PromptRepository
+
+ @Binds
+ @SysUISingleton
+ fun providesCredentialInteractor(impl: CredentialInteractorImpl): CredentialInteractor
+
+ companion object {
+ /** Background [Executor] for HAL related operations. */
+ @Provides
+ @SysUISingleton
+ @JvmStatic
+ @BiometricsBackground
+ fun providesPluginExecutor(threadFactory: ThreadFactory): Executor =
+ threadFactory.buildExecutorOnNewThread("biometrics")
+ }
}
/**
- * Background executor for HAL operations that are latency sensitive but too
- * slow to run on the main thread. Prefer the shared executors, such as
- * [com.android.systemui.dagger.qualifiers.Background] when a HAL is not directly involved.
+ * Background executor for HAL operations that are latency sensitive but too slow to run on the main
+ * thread. Prefer the shared executors, such as [com.android.systemui.dagger.qualifiers.Background]
+ * when a HAL is not directly involved.
*/
@Qualifier
@MustBeDocumented
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/model/PromptKind.kt
similarity index 64%
copy from packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
copy to packages/SystemUI/src/com/android/systemui/biometrics/data/model/PromptKind.kt
index 581dafa3..e82646f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/model/PromptKind.kt
@@ -14,10 +14,15 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.domain.model
+package com.android.systemui.biometrics.data.model
-/** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */
-enum class KeyguardQuickAffordancePosition {
- BOTTOM_START,
- BOTTOM_END,
+import com.android.systemui.biometrics.Utils
+
+// TODO(b/251476085): this should eventually replace Utils.CredentialType
+/** Credential options for biometric prompt. Shadows [Utils.CredentialType]. */
+enum class PromptKind {
+ ANY_BIOMETRIC,
+ PIN,
+ PATTERN,
+ PASSWORD,
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
new file mode 100644
index 0000000..92a13cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
@@ -0,0 +1,102 @@
+package com.android.systemui.biometrics.data.repository
+
+import android.hardware.biometrics.PromptInfo
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.data.model.PromptKind
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * A repository for the global state of BiometricPrompt.
+ *
+ * There is never more than one instance of the prompt at any given time.
+ */
+interface PromptRepository {
+
+ /** If the prompt is showing. */
+ val isShowing: Flow<Boolean>
+
+ /** The app-specific details to show in the prompt. */
+ val promptInfo: StateFlow<PromptInfo?>
+
+ /** The user that the prompt is for. */
+ val userId: StateFlow<Int?>
+
+ /** The gatekeeper challenge, if one is associated with this prompt. */
+ val challenge: StateFlow<Long?>
+
+ /** The kind of credential to use (biometric, pin, pattern, etc.). */
+ val kind: StateFlow<PromptKind>
+
+ /** Update the prompt configuration, which should be set before [isShowing]. */
+ fun setPrompt(
+ promptInfo: PromptInfo,
+ userId: Int,
+ gatekeeperChallenge: Long?,
+ kind: PromptKind = PromptKind.ANY_BIOMETRIC,
+ )
+
+ /** Unset the prompt info. */
+ fun unsetPrompt()
+}
+
+@SysUISingleton
+class PromptRepositoryImpl @Inject constructor(private val authController: AuthController) :
+ PromptRepository {
+
+ override val isShowing: Flow<Boolean> = conflatedCallbackFlow {
+ val callback =
+ object : AuthController.Callback {
+ override fun onBiometricPromptShown() =
+ trySendWithFailureLogging(true, TAG, "set isShowing")
+
+ override fun onBiometricPromptDismissed() =
+ trySendWithFailureLogging(false, TAG, "unset isShowing")
+ }
+ authController.addCallback(callback)
+ trySendWithFailureLogging(authController.isShowing, TAG, "update isShowing")
+ awaitClose { authController.removeCallback(callback) }
+ }
+
+ private val _promptInfo: MutableStateFlow<PromptInfo?> = MutableStateFlow(null)
+ override val promptInfo = _promptInfo.asStateFlow()
+
+ private val _challenge: MutableStateFlow<Long?> = MutableStateFlow(null)
+ override val challenge: StateFlow<Long?> = _challenge.asStateFlow()
+
+ private val _userId: MutableStateFlow<Int?> = MutableStateFlow(null)
+ override val userId = _userId.asStateFlow()
+
+ private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.ANY_BIOMETRIC)
+ override val kind = _kind.asStateFlow()
+
+ override fun setPrompt(
+ promptInfo: PromptInfo,
+ userId: Int,
+ gatekeeperChallenge: Long?,
+ kind: PromptKind,
+ ) {
+ _kind.value = kind
+ _userId.value = userId
+ _challenge.value = gatekeeperChallenge
+ _promptInfo.value = promptInfo
+ }
+
+ override fun unsetPrompt() {
+ _promptInfo.value = null
+ _userId.value = null
+ _challenge.value = null
+ _kind.value = PromptKind.ANY_BIOMETRIC
+ }
+
+ companion object {
+ private const val TAG = "BiometricPromptRepository"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
new file mode 100644
index 0000000..1f1a1b5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
@@ -0,0 +1,282 @@
+package com.android.systemui.biometrics.domain.interactor
+
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyResources
+import android.content.Context
+import android.os.UserManager
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockscreenCredential
+import com.android.internal.widget.VerifyCredentialResponse
+import com.android.systemui.R
+import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+
+/**
+ * A wrapper for [LockPatternUtils] to verify PIN, pattern, or password credentials.
+ *
+ * This class also uses the [DevicePolicyManager] to generate appropriate error messages when policy
+ * exceptions are raised (i.e. wipe device due to excessive failed attempts, etc.).
+ */
+interface CredentialInteractor {
+ /** If the user's pattern credential should be hidden */
+ fun isStealthModeActive(userId: Int): Boolean
+
+ /** Get the effective user id (profile owner, if one exists) */
+ fun getCredentialOwnerOrSelfId(userId: Int): Int
+
+ /**
+ * Verifies a credential and returns a stream of results.
+ *
+ * The final emitted value will either be a [CredentialStatus.Fail.Error] or a
+ * [CredentialStatus.Success.Verified].
+ */
+ fun verifyCredential(
+ request: BiometricPromptRequest.Credential,
+ credential: LockscreenCredential,
+ ): Flow<CredentialStatus>
+}
+
+/** Standard implementation of [CredentialInteractor]. */
+class CredentialInteractorImpl
+@Inject
+constructor(
+ @Application private val applicationContext: Context,
+ private val lockPatternUtils: LockPatternUtils,
+ private val userManager: UserManager,
+ private val devicePolicyManager: DevicePolicyManager,
+ private val systemClock: SystemClock,
+) : CredentialInteractor {
+
+ override fun isStealthModeActive(userId: Int): Boolean =
+ !lockPatternUtils.isVisiblePatternEnabled(userId)
+
+ override fun getCredentialOwnerOrSelfId(userId: Int): Int =
+ userManager.getCredentialOwnerProfile(userId)
+
+ override fun verifyCredential(
+ request: BiometricPromptRequest.Credential,
+ credential: LockscreenCredential,
+ ): Flow<CredentialStatus> = flow {
+ // Request LockSettingsService to return the Gatekeeper Password in the
+ // VerifyCredentialResponse so that we can request a Gatekeeper HAT with the
+ // Gatekeeper Password and operationId.
+ val effectiveUserId = request.userInfo.deviceCredentialOwnerId
+ val response =
+ lockPatternUtils.verifyCredential(
+ credential,
+ effectiveUserId,
+ LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE
+ )
+
+ if (response.isMatched) {
+ lockPatternUtils.userPresent(effectiveUserId)
+
+ // The response passed into this method contains the Gatekeeper
+ // Password. We still have to request Gatekeeper to create a
+ // Hardware Auth Token with the Gatekeeper Password and Challenge
+ // (keystore operationId in this case)
+ val pwHandle = response.gatekeeperPasswordHandle
+ val gkResponse: VerifyCredentialResponse =
+ lockPatternUtils.verifyGatekeeperPasswordHandle(
+ pwHandle,
+ request.operationInfo.gatekeeperChallenge,
+ effectiveUserId
+ )
+ val hat = gkResponse.gatekeeperHAT
+ lockPatternUtils.removeGatekeeperPasswordHandle(pwHandle)
+ emit(CredentialStatus.Success.Verified(hat))
+ } else if (response.timeout > 0) {
+ // if requests are being throttled, update the error message every
+ // second until the temporary lock has expired
+ val deadline: Long =
+ lockPatternUtils.setLockoutAttemptDeadline(effectiveUserId, response.timeout)
+ val interval = LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS
+ var remaining = deadline - systemClock.elapsedRealtime()
+ while (remaining > 0) {
+ emit(
+ CredentialStatus.Fail.Throttled(
+ applicationContext.getString(
+ R.string.biometric_dialog_credential_too_many_attempts,
+ remaining / 1000
+ )
+ )
+ )
+ delay(interval)
+ remaining -= interval
+ }
+ emit(CredentialStatus.Fail.Error(""))
+ } else { // bad request, but not throttled
+ val numAttempts = lockPatternUtils.getCurrentFailedPasswordAttempts(effectiveUserId) + 1
+ val maxAttempts = lockPatternUtils.getMaximumFailedPasswordsForWipe(effectiveUserId)
+ if (maxAttempts <= 0 || numAttempts <= 0) {
+ // use a generic message if there's no maximum number of attempts
+ emit(CredentialStatus.Fail.Error())
+ } else {
+ val remainingAttempts = (maxAttempts - numAttempts).coerceAtLeast(0)
+ emit(
+ CredentialStatus.Fail.Error(
+ applicationContext.getString(
+ R.string.biometric_dialog_credential_attempts_before_wipe,
+ numAttempts,
+ maxAttempts
+ ),
+ remainingAttempts,
+ fetchFinalAttemptMessageOrNull(request, remainingAttempts)
+ )
+ )
+ }
+ lockPatternUtils.reportFailedPasswordAttempt(effectiveUserId)
+ }
+ }
+
+ private fun fetchFinalAttemptMessageOrNull(
+ request: BiometricPromptRequest.Credential,
+ remainingAttempts: Int?,
+ ): String? =
+ if (remainingAttempts != null && remainingAttempts <= 1) {
+ applicationContext.getFinalAttemptMessageOrBlank(
+ request,
+ devicePolicyManager,
+ userManager.getUserTypeForWipe(
+ devicePolicyManager,
+ request.userInfo.deviceCredentialOwnerId
+ ),
+ remainingAttempts
+ )
+ } else {
+ null
+ }
+}
+
+private enum class UserType {
+ PRIMARY,
+ MANAGED_PROFILE,
+ SECONDARY,
+}
+
+private fun UserManager.getUserTypeForWipe(
+ devicePolicyManager: DevicePolicyManager,
+ effectiveUserId: Int,
+): UserType {
+ val userToBeWiped =
+ getUserInfo(
+ devicePolicyManager.getProfileWithMinimumFailedPasswordsForWipe(effectiveUserId)
+ )
+ return when {
+ userToBeWiped == null || userToBeWiped.isPrimary -> UserType.PRIMARY
+ userToBeWiped.isManagedProfile -> UserType.MANAGED_PROFILE
+ else -> UserType.SECONDARY
+ }
+}
+
+private fun Context.getFinalAttemptMessageOrBlank(
+ request: BiometricPromptRequest.Credential,
+ devicePolicyManager: DevicePolicyManager,
+ userType: UserType,
+ remaining: Int,
+): String =
+ when {
+ remaining == 1 -> getLastAttemptBeforeWipeMessage(request, devicePolicyManager, userType)
+ remaining <= 0 -> getNowWipingMessage(devicePolicyManager, userType)
+ else -> ""
+ }
+
+private fun Context.getLastAttemptBeforeWipeMessage(
+ request: BiometricPromptRequest.Credential,
+ devicePolicyManager: DevicePolicyManager,
+ userType: UserType,
+): String =
+ when (userType) {
+ UserType.PRIMARY -> getLastAttemptBeforeWipeDeviceMessage(request)
+ UserType.MANAGED_PROFILE ->
+ getLastAttemptBeforeWipeProfileMessage(request, devicePolicyManager)
+ UserType.SECONDARY -> getLastAttemptBeforeWipeUserMessage(request)
+ }
+
+private fun Context.getLastAttemptBeforeWipeDeviceMessage(
+ request: BiometricPromptRequest.Credential,
+): String {
+ val id =
+ when (request) {
+ is BiometricPromptRequest.Credential.Pin ->
+ R.string.biometric_dialog_last_pin_attempt_before_wipe_device
+ is BiometricPromptRequest.Credential.Pattern ->
+ R.string.biometric_dialog_last_pattern_attempt_before_wipe_device
+ is BiometricPromptRequest.Credential.Password ->
+ R.string.biometric_dialog_last_password_attempt_before_wipe_device
+ }
+ return getString(id)
+}
+
+private fun Context.getLastAttemptBeforeWipeProfileMessage(
+ request: BiometricPromptRequest.Credential,
+ devicePolicyManager: DevicePolicyManager,
+): String {
+ val id =
+ when (request) {
+ is BiometricPromptRequest.Credential.Pin ->
+ DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT
+ is BiometricPromptRequest.Credential.Pattern ->
+ DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT
+ is BiometricPromptRequest.Credential.Password ->
+ DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT
+ }
+ return devicePolicyManager.resources.getString(id) {
+ // use fallback a string if not found
+ val defaultId =
+ when (request) {
+ is BiometricPromptRequest.Credential.Pin ->
+ R.string.biometric_dialog_last_pin_attempt_before_wipe_profile
+ is BiometricPromptRequest.Credential.Pattern ->
+ R.string.biometric_dialog_last_pattern_attempt_before_wipe_profile
+ is BiometricPromptRequest.Credential.Password ->
+ R.string.biometric_dialog_last_password_attempt_before_wipe_profile
+ }
+ getString(defaultId)
+ }
+}
+
+private fun Context.getLastAttemptBeforeWipeUserMessage(
+ request: BiometricPromptRequest.Credential,
+): String {
+ val resId =
+ when (request) {
+ is BiometricPromptRequest.Credential.Pin ->
+ R.string.biometric_dialog_last_pin_attempt_before_wipe_user
+ is BiometricPromptRequest.Credential.Pattern ->
+ R.string.biometric_dialog_last_pattern_attempt_before_wipe_user
+ is BiometricPromptRequest.Credential.Password ->
+ R.string.biometric_dialog_last_password_attempt_before_wipe_user
+ }
+ return getString(resId)
+}
+
+private fun Context.getNowWipingMessage(
+ devicePolicyManager: DevicePolicyManager,
+ userType: UserType,
+): String {
+ val id =
+ when (userType) {
+ UserType.MANAGED_PROFILE ->
+ DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS
+ else -> DevicePolicyResources.UNDEFINED
+ }
+ return devicePolicyManager.resources.getString(id) {
+ // use fallback a string if not found
+ val defaultId =
+ when (userType) {
+ UserType.PRIMARY ->
+ com.android.settingslib.R.string.failed_attempts_now_wiping_device
+ UserType.MANAGED_PROFILE ->
+ com.android.settingslib.R.string.failed_attempts_now_wiping_profile
+ UserType.SECONDARY ->
+ com.android.settingslib.R.string.failed_attempts_now_wiping_user
+ }
+ getString(defaultId)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialStatus.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialStatus.kt
new file mode 100644
index 0000000..40b7612
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialStatus.kt
@@ -0,0 +1,23 @@
+package com.android.systemui.biometrics.domain.interactor
+
+/** Result of a [CredentialInteractor.verifyCredential] check. */
+sealed interface CredentialStatus {
+ /** A successful result. */
+ sealed interface Success : CredentialStatus {
+ /** The credential is valid and a [hat] has been generated. */
+ data class Verified(val hat: ByteArray) : Success
+ }
+ /** A failed result. */
+ sealed interface Fail : CredentialStatus {
+ val error: String?
+
+ /** The credential check failed with an [error]. */
+ data class Error(
+ override val error: String? = null,
+ val remainingAttempts: Int? = null,
+ val urgentMessage: String? = null,
+ ) : Fail
+ /** The credential check failed with an [error] and is temporarily locked out. */
+ data class Throttled(override val error: String) : Fail
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
new file mode 100644
index 0000000..6362c2f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
@@ -0,0 +1,189 @@
+package com.android.systemui.biometrics.domain.interactor
+
+import android.hardware.biometrics.PromptInfo
+import com.android.internal.widget.LockPatternView
+import com.android.internal.widget.LockscreenCredential
+import com.android.systemui.biometrics.Utils
+import com.android.systemui.biometrics.data.model.PromptKind
+import com.android.systemui.biometrics.data.repository.PromptRepository
+import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
+import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
+import com.android.systemui.biometrics.domain.model.BiometricUserInfo
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.lastOrNull
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.withContext
+
+/**
+ * Business logic for BiometricPrompt's CredentialViews, which primarily includes checking a users
+ * PIN, pattern, or password credential instead of a biometric.
+ */
+class BiometricPromptCredentialInteractor
+@Inject
+constructor(
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ private val biometricPromptRepository: PromptRepository,
+ private val credentialInteractor: CredentialInteractor,
+) {
+ /** If the prompt is currently showing. */
+ val isShowing: Flow<Boolean> = biometricPromptRepository.isShowing
+
+ /** Metadata about the current credential prompt, including app-supplied preferences. */
+ val prompt: Flow<BiometricPromptRequest?> =
+ combine(
+ biometricPromptRepository.promptInfo,
+ biometricPromptRepository.challenge,
+ biometricPromptRepository.userId,
+ biometricPromptRepository.kind
+ ) { promptInfo, challenge, userId, kind ->
+ if (promptInfo == null || userId == null || challenge == null) {
+ return@combine null
+ }
+
+ when (kind) {
+ PromptKind.PIN ->
+ BiometricPromptRequest.Credential.Pin(
+ info = promptInfo,
+ userInfo = userInfo(userId),
+ operationInfo = operationInfo(challenge)
+ )
+ PromptKind.PATTERN ->
+ BiometricPromptRequest.Credential.Pattern(
+ info = promptInfo,
+ userInfo = userInfo(userId),
+ operationInfo = operationInfo(challenge),
+ stealthMode = credentialInteractor.isStealthModeActive(userId)
+ )
+ PromptKind.PASSWORD ->
+ BiometricPromptRequest.Credential.Password(
+ info = promptInfo,
+ userInfo = userInfo(userId),
+ operationInfo = operationInfo(challenge)
+ )
+ else -> null
+ }
+ }
+ .distinctUntilChanged()
+
+ private fun userInfo(userId: Int): BiometricUserInfo =
+ BiometricUserInfo(
+ userId = userId,
+ deviceCredentialOwnerId = credentialInteractor.getCredentialOwnerOrSelfId(userId)
+ )
+
+ private fun operationInfo(challenge: Long): BiometricOperationInfo =
+ BiometricOperationInfo(gatekeeperChallenge = challenge)
+
+ /** Most recent error due to [verifyCredential]. */
+ private val _verificationError = MutableStateFlow<CredentialStatus.Fail?>(null)
+ val verificationError: Flow<CredentialStatus.Fail?> = _verificationError.asStateFlow()
+
+ /** Update the current request to use credential-based authentication instead of biometrics. */
+ fun useCredentialsForAuthentication(
+ promptInfo: PromptInfo,
+ @Utils.CredentialType kind: Int,
+ userId: Int,
+ challenge: Long,
+ ) {
+ biometricPromptRepository.setPrompt(
+ promptInfo,
+ userId,
+ challenge,
+ kind.asBiometricPromptCredential()
+ )
+ }
+
+ /** Unset the current authentication request. */
+ fun resetPrompt() {
+ biometricPromptRepository.unsetPrompt()
+ }
+
+ /**
+ * Check a credential and return the attestation token (HAT) if successful.
+ *
+ * This method will not return if credential checks are being throttled until the throttling has
+ * expired and the user can try again. It will periodically update the [verificationError] until
+ * cancelled or the throttling has completed. If the request is not throttled, but unsuccessful,
+ * the [verificationError] will be set and an optional
+ * [CredentialStatus.Fail.Error.urgentMessage] message may be provided to indicate additional
+ * hints to the user (i.e. device will be wiped on next failure, etc.).
+ *
+ * The check happens on the background dispatcher given in the constructor.
+ */
+ suspend fun checkCredential(
+ request: BiometricPromptRequest.Credential,
+ text: CharSequence? = null,
+ pattern: List<LockPatternView.Cell>? = null,
+ ): CredentialStatus =
+ withContext(bgDispatcher) {
+ val credential =
+ when (request) {
+ is BiometricPromptRequest.Credential.Pin ->
+ LockscreenCredential.createPinOrNone(text ?: "")
+ is BiometricPromptRequest.Credential.Password ->
+ LockscreenCredential.createPasswordOrNone(text ?: "")
+ is BiometricPromptRequest.Credential.Pattern ->
+ LockscreenCredential.createPattern(pattern ?: listOf())
+ }
+
+ credential.use { c -> verifyCredential(request, c) }
+ }
+
+ private suspend fun verifyCredential(
+ request: BiometricPromptRequest.Credential,
+ credential: LockscreenCredential?
+ ): CredentialStatus {
+ if (credential == null || credential.isNone) {
+ return CredentialStatus.Fail.Error()
+ }
+
+ val finalStatus =
+ credentialInteractor
+ .verifyCredential(request, credential)
+ .onEach { status ->
+ when (status) {
+ is CredentialStatus.Success -> _verificationError.value = null
+ is CredentialStatus.Fail -> _verificationError.value = status
+ }
+ }
+ .lastOrNull()
+
+ return finalStatus ?: CredentialStatus.Fail.Error()
+ }
+
+ /**
+ * Report a user-visible error.
+ *
+ * Use this instead of calling [verifyCredential] when it is not necessary because the check
+ * will obviously fail (i.e. too short, empty, etc.)
+ */
+ fun setVerificationError(error: CredentialStatus.Fail.Error?) {
+ if (error != null) {
+ _verificationError.value = error
+ } else {
+ resetVerificationError()
+ }
+ }
+
+ /** Clear the current error message, if any. */
+ fun resetVerificationError() {
+ _verificationError.value = null
+ }
+}
+
+// TODO(b/251476085): remove along with Utils.CredentialType
+/** Convert a [Utils.CredentialType] to the corresponding [PromptKind]. */
+private fun @receiver:Utils.CredentialType Int.asBiometricPromptCredential(): PromptKind =
+ when (this) {
+ Utils.CREDENTIAL_PIN -> PromptKind.PIN
+ Utils.CREDENTIAL_PASSWORD -> PromptKind.PASSWORD
+ Utils.CREDENTIAL_PATTERN -> PromptKind.PATTERN
+ else -> PromptKind.ANY_BIOMETRIC
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricOperationInfo.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricOperationInfo.kt
new file mode 100644
index 0000000..c619b12
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricOperationInfo.kt
@@ -0,0 +1,4 @@
+package com.android.systemui.biometrics.domain.model
+
+/** Metadata about an in-progress biometric operation. */
+data class BiometricOperationInfo(val gatekeeperChallenge: Long = -1)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
new file mode 100644
index 0000000..5ee0381
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
@@ -0,0 +1,69 @@
+package com.android.systemui.biometrics.domain.model
+
+import android.hardware.biometrics.PromptInfo
+
+/**
+ * Preferences for BiometricPrompt, such as title & description, that are immutable while the prompt
+ * is showing.
+ *
+ * This roughly corresponds to a "request" by the system or an app to show BiometricPrompt and it
+ * contains a subset of the information in a [PromptInfo] that is relevant to SysUI.
+ */
+sealed class BiometricPromptRequest(
+ val title: String,
+ val subtitle: String,
+ val description: String,
+ val userInfo: BiometricUserInfo,
+ val operationInfo: BiometricOperationInfo,
+) {
+ /** Prompt using one or more biometrics. */
+ class Biometric(
+ info: PromptInfo,
+ userInfo: BiometricUserInfo,
+ operationInfo: BiometricOperationInfo,
+ ) :
+ BiometricPromptRequest(
+ title = info.title?.toString() ?: "",
+ subtitle = info.subtitle?.toString() ?: "",
+ description = info.description?.toString() ?: "",
+ userInfo = userInfo,
+ operationInfo = operationInfo
+ )
+
+ /** Prompt using a credential (pin, pattern, password). */
+ sealed class Credential(
+ info: PromptInfo,
+ userInfo: BiometricUserInfo,
+ operationInfo: BiometricOperationInfo,
+ ) :
+ BiometricPromptRequest(
+ title = (info.deviceCredentialTitle ?: info.title)?.toString() ?: "",
+ subtitle = (info.deviceCredentialSubtitle ?: info.subtitle)?.toString() ?: "",
+ description = (info.deviceCredentialDescription ?: info.description)?.toString() ?: "",
+ userInfo = userInfo,
+ operationInfo = operationInfo,
+ ) {
+
+ /** PIN prompt. */
+ class Pin(
+ info: PromptInfo,
+ userInfo: BiometricUserInfo,
+ operationInfo: BiometricOperationInfo,
+ ) : Credential(info, userInfo, operationInfo)
+
+ /** Password prompt. */
+ class Password(
+ info: PromptInfo,
+ userInfo: BiometricUserInfo,
+ operationInfo: BiometricOperationInfo,
+ ) : Credential(info, userInfo, operationInfo)
+
+ /** Pattern prompt. */
+ class Pattern(
+ info: PromptInfo,
+ userInfo: BiometricUserInfo,
+ operationInfo: BiometricOperationInfo,
+ val stealthMode: Boolean,
+ ) : Credential(info, userInfo, operationInfo)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricUserInfo.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricUserInfo.kt
new file mode 100644
index 0000000..08da04d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricUserInfo.kt
@@ -0,0 +1,7 @@
+package com.android.systemui.biometrics.domain.model
+
+/** Metadata about the current user BiometricPrompt is being shown to. */
+data class BiometricUserInfo(
+ val userId: Int,
+ val deviceCredentialOwnerId: Int = userId,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
new file mode 100644
index 0000000..bcc0575
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
@@ -0,0 +1,130 @@
+package com.android.systemui.biometrics.ui
+
+import android.content.Context
+import android.content.res.Configuration.ORIENTATION_LANDSCAPE
+import android.text.TextUtils
+import android.util.AttributeSet
+import android.view.View
+import android.view.WindowInsets
+import android.view.WindowInsets.Type.ime
+import android.view.accessibility.AccessibilityManager
+import android.widget.ImageView
+import android.widget.ImeAwareEditText
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.core.view.isGone
+import com.android.systemui.R
+import com.android.systemui.biometrics.AuthPanelController
+import com.android.systemui.biometrics.ui.binder.CredentialViewBinder
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
+
+/** PIN or password credential view for BiometricPrompt. */
+class CredentialPasswordView(context: Context, attrs: AttributeSet?) :
+ LinearLayout(context, attrs), CredentialView, View.OnApplyWindowInsetsListener {
+
+ private lateinit var titleView: TextView
+ private lateinit var subtitleView: TextView
+ private lateinit var descriptionView: TextView
+ private lateinit var iconView: ImageView
+ private lateinit var passwordField: ImeAwareEditText
+ private lateinit var credentialHeader: View
+ private lateinit var credentialInput: View
+
+ private var bottomInset: Int = 0
+
+ private val accessibilityManager by lazy {
+ context.getSystemService(AccessibilityManager::class.java)
+ }
+
+ /** Initializes the view. */
+ override fun init(
+ viewModel: CredentialViewModel,
+ host: CredentialView.Host,
+ panelViewController: AuthPanelController,
+ animatePanel: Boolean,
+ ) {
+ CredentialViewBinder.bind(this, host, viewModel, panelViewController, animatePanel)
+ }
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+
+ titleView = requireViewById(R.id.title)
+ subtitleView = requireViewById(R.id.subtitle)
+ descriptionView = requireViewById(R.id.description)
+ iconView = requireViewById(R.id.icon)
+ subtitleView = requireViewById(R.id.subtitle)
+ passwordField = requireViewById(R.id.lockPassword)
+ credentialHeader = requireViewById(R.id.auth_credential_header)
+ credentialInput = requireViewById(R.id.auth_credential_input)
+
+ setOnApplyWindowInsetsListener(this)
+ }
+
+ override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
+ super.onLayout(changed, left, top, right, bottom)
+
+ val inputLeftBound: Int
+ val inputTopBound: Int
+ var headerRightBound = right
+ var headerTopBounds = top
+ val subTitleBottom: Int = if (subtitleView.isGone) titleView.bottom else subtitleView.bottom
+ val descBottom = if (descriptionView.isGone) subTitleBottom else descriptionView.bottom
+ if (resources.configuration.orientation == ORIENTATION_LANDSCAPE) {
+ inputTopBound = (bottom - credentialInput.height) / 2
+ inputLeftBound = (right - left) / 2
+ headerRightBound = inputLeftBound
+ headerTopBounds -= iconView.bottom.coerceAtMost(bottomInset)
+ } else {
+ inputTopBound = descBottom + (bottom - descBottom - credentialInput.height) / 2
+ inputLeftBound = (right - left - credentialInput.width) / 2
+ }
+
+ if (descriptionView.bottom > bottomInset) {
+ credentialHeader.layout(left, headerTopBounds, headerRightBound, bottom)
+ }
+ credentialInput.layout(inputLeftBound, inputTopBound, right, bottom)
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+
+ val newWidth = MeasureSpec.getSize(widthMeasureSpec)
+ val newHeight = MeasureSpec.getSize(heightMeasureSpec) - bottomInset
+
+ setMeasuredDimension(newWidth, newHeight)
+
+ val halfWidthSpec = MeasureSpec.makeMeasureSpec(width / 2, MeasureSpec.AT_MOST)
+ val fullHeightSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.UNSPECIFIED)
+ if (resources.configuration.orientation == ORIENTATION_LANDSCAPE) {
+ measureChildren(halfWidthSpec, fullHeightSpec)
+ } else {
+ measureChildren(widthMeasureSpec, fullHeightSpec)
+ }
+ }
+
+ override fun onApplyWindowInsets(v: View, insets: WindowInsets): WindowInsets {
+ val bottomInsets = insets.getInsets(ime())
+ if (bottomInset != bottomInsets.bottom) {
+ bottomInset = bottomInsets.bottom
+
+ if (bottomInset > 0 && resources.configuration.orientation == ORIENTATION_LANDSCAPE) {
+ titleView.isSingleLine = true
+ titleView.ellipsize = TextUtils.TruncateAt.MARQUEE
+ titleView.marqueeRepeatLimit = -1
+ // select to enable marquee unless a screen reader is enabled
+ titleView.isSelected = accessibilityManager.shouldMarquee()
+ } else {
+ titleView.isSingleLine = false
+ titleView.ellipsize = null
+ // select to enable marquee unless a screen reader is enabled
+ titleView.isSelected = false
+ }
+
+ requestLayout()
+ }
+ return insets
+ }
+}
+
+private fun AccessibilityManager.shouldMarquee(): Boolean = !isEnabled || !isTouchExplorationEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPatternView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPatternView.kt
new file mode 100644
index 0000000..75331f0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPatternView.kt
@@ -0,0 +1,23 @@
+package com.android.systemui.biometrics.ui
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.LinearLayout
+import com.android.systemui.biometrics.AuthPanelController
+import com.android.systemui.biometrics.ui.binder.CredentialViewBinder
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
+
+/** Pattern credential view for BiometricPrompt. */
+class CredentialPatternView(context: Context, attrs: AttributeSet?) :
+ LinearLayout(context, attrs), CredentialView {
+
+ /** Initializes the view. */
+ override fun init(
+ viewModel: CredentialViewModel,
+ host: CredentialView.Host,
+ panelViewController: AuthPanelController,
+ animatePanel: Boolean,
+ ) {
+ CredentialViewBinder.bind(this, host, viewModel, panelViewController, animatePanel)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialView.kt
new file mode 100644
index 0000000..b7c6a45
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialView.kt
@@ -0,0 +1,31 @@
+package com.android.systemui.biometrics.ui
+
+import com.android.systemui.biometrics.AuthPanelController
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
+
+/** A credential variant of BiometricPrompt. */
+sealed interface CredentialView {
+ /**
+ * Callbacks for the "host" container view that contains this credential view.
+ *
+ * TODO(b/251476085): Removed when the host view is converted to use a parent view model.
+ */
+ interface Host {
+ /** When the user's credential has been verified. */
+ fun onCredentialMatched(attestation: ByteArray)
+
+ /** When the user abandons credential verification. */
+ fun onCredentialAborted()
+
+ /** Warn the user is warned about excessive attempts. */
+ fun onCredentialAttemptsRemaining(remaining: Int, messageBody: String)
+ }
+
+ // TODO(251476085): remove AuthPanelController
+ fun init(
+ viewModel: CredentialViewModel,
+ host: Host,
+ panelViewController: AuthPanelController,
+ animatePanel: Boolean,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
new file mode 100644
index 0000000..c619648
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
@@ -0,0 +1,104 @@
+package com.android.systemui.biometrics.ui.binder
+
+import android.view.KeyEvent
+import android.view.View
+import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.InputMethodManager
+import android.widget.ImeAwareEditText
+import android.widget.TextView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.biometrics.ui.CredentialPasswordView
+import com.android.systemui.biometrics.ui.CredentialView
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+/** Sub-binder for the [CredentialPasswordView]. */
+object CredentialPasswordViewBinder {
+
+ /** Bind the view. */
+ fun bind(
+ view: CredentialPasswordView,
+ host: CredentialView.Host,
+ viewModel: CredentialViewModel,
+ ) {
+ val imeManager = view.context.getSystemService(InputMethodManager::class.java)!!
+
+ val passwordField: ImeAwareEditText = view.requireViewById(R.id.lockPassword)
+
+ view.repeatWhenAttached {
+ passwordField.requestFocus()
+ passwordField.scheduleShowSoftInput()
+
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ // observe credential validation attempts and submit/cancel buttons
+ launch {
+ viewModel.header.collect { header ->
+ passwordField.setTextOperationUser(header.user)
+ passwordField.setOnEditorActionListener(
+ OnImeSubmitListener { text ->
+ launch { viewModel.checkCredential(text, header) }
+ }
+ )
+ passwordField.setOnKeyListener(
+ OnBackButtonListener { host.onCredentialAborted() }
+ )
+ }
+ }
+
+ launch {
+ viewModel.inputFlags.collect { flags ->
+ flags?.let { passwordField.inputType = it }
+ }
+ }
+
+ // dismiss on a valid credential check
+ launch {
+ viewModel.validatedAttestation.collect { attestation ->
+ if (attestation != null) {
+ imeManager.hideSoftInputFromWindow(view.windowToken, 0 /* flags */)
+ host.onCredentialMatched(attestation)
+ } else {
+ passwordField.setText("")
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+private class OnBackButtonListener(private val onBack: () -> Unit) : View.OnKeyListener {
+ override fun onKey(v: View, keyCode: Int, event: KeyEvent): Boolean {
+ if (keyCode != KeyEvent.KEYCODE_BACK) {
+ return false
+ }
+ if (event.action == KeyEvent.ACTION_UP) {
+ onBack()
+ }
+ return true
+ }
+}
+
+private class OnImeSubmitListener(private val onSubmit: (text: CharSequence) -> Unit) :
+ TextView.OnEditorActionListener {
+ override fun onEditorAction(v: TextView, actionId: Int, event: KeyEvent?): Boolean {
+ val isSoftImeEvent =
+ event == null &&
+ (actionId == EditorInfo.IME_NULL ||
+ actionId == EditorInfo.IME_ACTION_DONE ||
+ actionId == EditorInfo.IME_ACTION_NEXT)
+ val isKeyboardEnterKey =
+ event != null &&
+ KeyEvent.isConfirmKey(event.keyCode) &&
+ event.action == KeyEvent.ACTION_DOWN
+ if (isSoftImeEvent || isKeyboardEnterKey) {
+ onSubmit(v.text)
+ return true
+ }
+ return false
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt
new file mode 100644
index 0000000..4765551
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt
@@ -0,0 +1,75 @@
+package com.android.systemui.biometrics.ui.binder
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternView
+import com.android.systemui.R
+import com.android.systemui.biometrics.ui.CredentialPatternView
+import com.android.systemui.biometrics.ui.CredentialView
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+/** Sub-binder for the [CredentialPatternView]. */
+object CredentialPatternViewBinder {
+
+ /** Bind the view. */
+ fun bind(
+ view: CredentialPatternView,
+ host: CredentialView.Host,
+ viewModel: CredentialViewModel,
+ ) {
+ val lockPatternView: LockPatternView = view.requireViewById(R.id.lockPattern)
+
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ // observe credential validation attempts and submit/cancel buttons
+ launch {
+ viewModel.header.collect { header ->
+ lockPatternView.setOnPatternListener(
+ OnPatternDetectedListener { pattern ->
+ if (pattern.isPatternLongEnough()) {
+ // Pattern size is less than the minimum
+ // do not count it as a failed attempt
+ viewModel.showPatternTooShortError()
+ } else {
+ lockPatternView.isEnabled = false
+ launch { viewModel.checkCredential(pattern, header) }
+ }
+ }
+ )
+ }
+ }
+
+ launch { viewModel.stealthMode.collect { lockPatternView.isInStealthMode = it } }
+
+ // dismiss on a valid credential check
+ launch {
+ viewModel.validatedAttestation.collect { attestation ->
+ val matched = attestation != null
+ lockPatternView.isEnabled = !matched
+ if (matched) {
+ host.onCredentialMatched(attestation!!)
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+private class OnPatternDetectedListener(
+ private val onDetected: (pattern: List<LockPatternView.Cell>) -> Unit
+) : LockPatternView.OnPatternListener {
+ override fun onPatternCellAdded(pattern: List<LockPatternView.Cell>) {}
+ override fun onPatternCleared() {}
+ override fun onPatternStart() {}
+ override fun onPatternDetected(pattern: List<LockPatternView.Cell>) {
+ onDetected(pattern)
+ }
+}
+
+private fun List<LockPatternView.Cell>.isPatternLongEnough(): Boolean =
+ size < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
new file mode 100644
index 0000000..fcc9487
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
@@ -0,0 +1,140 @@
+package com.android.systemui.biometrics.ui.binder
+
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.biometrics.AuthDialog
+import com.android.systemui.biometrics.AuthPanelController
+import com.android.systemui.biometrics.ui.CredentialPasswordView
+import com.android.systemui.biometrics.ui.CredentialPatternView
+import com.android.systemui.biometrics.ui.CredentialView
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+
+/**
+ * View binder for all credential variants of BiometricPrompt, including [CredentialPatternView] and
+ * [CredentialPasswordView].
+ *
+ * This binder delegates to sub-binders for each variant, such as the [CredentialPasswordViewBinder]
+ * and [CredentialPatternViewBinder].
+ */
+object CredentialViewBinder {
+
+ /** Binds a [CredentialPasswordView] or [CredentialPatternView] to a [CredentialViewModel]. */
+ @JvmStatic
+ fun bind(
+ view: ViewGroup,
+ host: CredentialView.Host,
+ viewModel: CredentialViewModel,
+ panelViewController: AuthPanelController,
+ animatePanel: Boolean,
+ maxErrorDuration: Long = 3_000L,
+ ) {
+ val titleView: TextView = view.requireViewById(R.id.title)
+ val subtitleView: TextView = view.requireViewById(R.id.subtitle)
+ val descriptionView: TextView = view.requireViewById(R.id.description)
+ val iconView: ImageView? = view.findViewById(R.id.icon)
+ val errorView: TextView = view.requireViewById(R.id.error)
+
+ var errorTimer: Job? = null
+
+ // bind common elements
+ view.repeatWhenAttached {
+ if (animatePanel) {
+ with(panelViewController) {
+ // Credential view is always full screen.
+ setUseFullScreen(true)
+ updateForContentDimensions(
+ containerWidth,
+ containerHeight,
+ 0 /* animateDurationMs */
+ )
+ }
+ }
+
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ // show prompt metadata
+ launch {
+ viewModel.header.collect { header ->
+ titleView.text = header.title
+ view.announceForAccessibility(header.title)
+
+ subtitleView.textOrHide = header.subtitle
+ descriptionView.textOrHide = header.description
+
+ iconView?.setImageDrawable(header.icon)
+
+ // Only animate this if we're transitioning from a biometric view.
+ if (viewModel.animateContents.value) {
+ view.animateCredentialViewIn()
+ }
+ }
+ }
+
+ // show transient error messages
+ launch {
+ viewModel.errorMessage
+ .onEach { msg ->
+ errorTimer?.cancel()
+ if (msg.isNotBlank()) {
+ errorTimer = launch {
+ delay(maxErrorDuration)
+ viewModel.resetErrorMessage()
+ }
+ }
+ }
+ .collect { errorView.textOrHide = it }
+ }
+
+ // show an extra dialog if the remaining attempts becomes low
+ launch {
+ viewModel.remainingAttempts
+ .filter { it.remaining != null }
+ .collect { info ->
+ host.onCredentialAttemptsRemaining(info.remaining!!, info.message)
+ }
+ }
+ }
+ }
+
+ // bind the auth widget
+ when (view) {
+ is CredentialPasswordView -> CredentialPasswordViewBinder.bind(view, host, viewModel)
+ is CredentialPatternView -> CredentialPatternViewBinder.bind(view, host, viewModel)
+ else -> throw IllegalStateException("unexpected view type: ${view.javaClass.name}")
+ }
+ }
+}
+
+private fun View.animateCredentialViewIn() {
+ translationY = resources.getDimension(R.dimen.biometric_dialog_credential_translation_offset)
+ alpha = 0f
+ postOnAnimation {
+ animate()
+ .translationY(0f)
+ .setDuration(AuthDialog.ANIMATE_CREDENTIAL_INITIAL_DURATION_MS.toLong())
+ .alpha(1f)
+ .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
+ .withLayer()
+ .start()
+ }
+}
+
+private var TextView.textOrHide: String?
+ set(value) {
+ val gone = value.isNullOrBlank()
+ visibility = if (gone) View.GONE else View.VISIBLE
+ text = if (gone) "" else value
+ }
+ get() = text?.toString()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
new file mode 100644
index 0000000..84bbceb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
@@ -0,0 +1,178 @@
+package com.android.systemui.biometrics.ui.viewmodel
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import android.text.InputType
+import com.android.internal.widget.LockPatternView
+import com.android.systemui.R
+import com.android.systemui.biometrics.Utils
+import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor
+import com.android.systemui.biometrics.domain.interactor.CredentialStatus
+import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlin.reflect.KClass
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.map
+
+/** View-model for all CredentialViews within BiometricPrompt. */
+class CredentialViewModel
+@Inject
+constructor(
+ @Application private val applicationContext: Context,
+ private val credentialInteractor: BiometricPromptCredentialInteractor,
+) {
+
+ /** Top level information about the prompt. */
+ val header: Flow<HeaderViewModel> =
+ credentialInteractor.prompt.filterIsInstance<BiometricPromptRequest.Credential>().map {
+ request ->
+ BiometricPromptHeaderViewModelImpl(
+ request,
+ user = UserHandle.of(request.userInfo.userId),
+ title = request.title,
+ subtitle = request.subtitle,
+ description = request.description,
+ icon = applicationContext.asLockIcon(request.userInfo.deviceCredentialOwnerId),
+ )
+ }
+
+ /** Input flags for text based credential views */
+ val inputFlags: Flow<Int?> =
+ credentialInteractor.prompt.map {
+ when (it) {
+ is BiometricPromptRequest.Credential.Pin ->
+ InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD
+ else -> null
+ }
+ }
+
+ /** If stealth mode is active (hide user credential input). */
+ val stealthMode: Flow<Boolean> =
+ credentialInteractor.prompt.map {
+ when (it) {
+ is BiometricPromptRequest.Credential.Pattern -> it.stealthMode
+ else -> false
+ }
+ }
+
+ private val _animateContents: MutableStateFlow<Boolean> = MutableStateFlow(true)
+ /** If this view should be animated on transitions. */
+ val animateContents = _animateContents.asStateFlow()
+
+ /** Error messages to show the user. */
+ val errorMessage: Flow<String> =
+ combine(credentialInteractor.verificationError, credentialInteractor.prompt) { error, p ->
+ when (error) {
+ is CredentialStatus.Fail.Error -> error.error
+ ?: applicationContext.asBadCredentialErrorMessage(p)
+ is CredentialStatus.Fail.Throttled -> error.error
+ null -> ""
+ }
+ }
+
+ private val _validatedAttestation: MutableSharedFlow<ByteArray?> = MutableSharedFlow()
+ /** Results of [checkPatternCredential]. A non-null attestation is supplied on success. */
+ val validatedAttestation: Flow<ByteArray?> = _validatedAttestation.asSharedFlow()
+
+ private val _remainingAttempts: MutableStateFlow<RemainingAttempts> =
+ MutableStateFlow(RemainingAttempts())
+ /** If set, the number of remaining attempts before the user must stop. */
+ val remainingAttempts: Flow<RemainingAttempts> = _remainingAttempts.asStateFlow()
+
+ /** Enable transition animations. */
+ fun setAnimateContents(animate: Boolean) {
+ _animateContents.value = animate
+ }
+
+ /** Show an error message to inform the user the pattern is too short to attempt validation. */
+ fun showPatternTooShortError() {
+ credentialInteractor.setVerificationError(
+ CredentialStatus.Fail.Error(
+ applicationContext.asBadCredentialErrorMessage(
+ BiometricPromptRequest.Credential.Pattern::class
+ )
+ )
+ )
+ }
+
+ /** Reset the error message to an empty string. */
+ fun resetErrorMessage() {
+ credentialInteractor.resetVerificationError()
+ }
+
+ /** Check a PIN or password and update [validatedAttestation] or [remainingAttempts]. */
+ suspend fun checkCredential(text: CharSequence, header: HeaderViewModel) =
+ checkCredential(credentialInteractor.checkCredential(header.asRequest(), text = text))
+
+ /** Check a pattern and update [validatedAttestation] or [remainingAttempts]. */
+ suspend fun checkCredential(pattern: List<LockPatternView.Cell>, header: HeaderViewModel) =
+ checkCredential(credentialInteractor.checkCredential(header.asRequest(), pattern = pattern))
+
+ private suspend fun checkCredential(result: CredentialStatus) {
+ when (result) {
+ is CredentialStatus.Success.Verified -> {
+ _validatedAttestation.emit(result.hat)
+ _remainingAttempts.value = RemainingAttempts()
+ }
+ is CredentialStatus.Fail.Error -> {
+ _validatedAttestation.emit(null)
+ _remainingAttempts.value =
+ RemainingAttempts(result.remainingAttempts, result.urgentMessage ?: "")
+ }
+ is CredentialStatus.Fail.Throttled -> {
+ // required for completeness, but a throttled error cannot be the final result
+ _validatedAttestation.emit(null)
+ _remainingAttempts.value = RemainingAttempts()
+ }
+ }
+ }
+}
+
+private fun Context.asBadCredentialErrorMessage(prompt: BiometricPromptRequest?): String =
+ asBadCredentialErrorMessage(
+ if (prompt != null) prompt::class else BiometricPromptRequest.Credential.Password::class
+ )
+
+private fun <T : BiometricPromptRequest> Context.asBadCredentialErrorMessage(
+ clazz: KClass<T>
+): String =
+ getString(
+ when (clazz) {
+ BiometricPromptRequest.Credential.Pin::class -> R.string.biometric_dialog_wrong_pin
+ BiometricPromptRequest.Credential.Password::class ->
+ R.string.biometric_dialog_wrong_password
+ BiometricPromptRequest.Credential.Pattern::class ->
+ R.string.biometric_dialog_wrong_pattern
+ else -> R.string.biometric_dialog_wrong_password
+ }
+ )
+
+private fun Context.asLockIcon(userId: Int): Drawable {
+ val id =
+ if (Utils.isManagedProfile(this, userId)) {
+ R.drawable.auth_dialog_enterprise
+ } else {
+ R.drawable.auth_dialog_lock
+ }
+ return resources.getDrawable(id, theme)
+}
+
+private class BiometricPromptHeaderViewModelImpl(
+ val request: BiometricPromptRequest.Credential,
+ override val user: UserHandle,
+ override val title: String,
+ override val subtitle: String,
+ override val description: String,
+ override val icon: Drawable,
+) : HeaderViewModel
+
+private fun HeaderViewModel.asRequest(): BiometricPromptRequest.Credential =
+ (this as BiometricPromptHeaderViewModelImpl).request
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/HeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/HeaderViewModel.kt
new file mode 100644
index 0000000..ba23f1c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/HeaderViewModel.kt
@@ -0,0 +1,13 @@
+package com.android.systemui.biometrics.ui.viewmodel
+
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+
+/** View model for the top-level header / info area of BiometricPrompt. */
+interface HeaderViewModel {
+ val user: UserHandle
+ val title: String
+ val subtitle: String
+ val description: String
+ val icon: Drawable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/RemainingAttempts.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/RemainingAttempts.kt
new file mode 100644
index 0000000..0f22173
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/RemainingAttempts.kt
@@ -0,0 +1,4 @@
+package com.android.systemui.biometrics.ui.viewmodel
+
+/** Metadata about the number of credential attempts the user has left [remaining], if known. */
+data class RemainingAttempts(val remaining: Int? = null, val message: String = "")
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index e47e636..6db56210 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -84,6 +84,7 @@
import com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule;
import com.android.systemui.statusbar.window.StatusBarWindowModule;
import com.android.systemui.telephony.data.repository.TelephonyRepositoryModule;
+import com.android.systemui.temporarydisplay.dagger.TemporaryDisplayModule;
import com.android.systemui.tuner.dagger.TunerModule;
import com.android.systemui.unfold.SysUIUnfoldModule;
import com.android.systemui.user.UserModule;
@@ -150,6 +151,7 @@
SysUIConcurrencyModule.class,
SysUIUnfoldModule.class,
TelephonyRepositoryModule.class,
+ TemporaryDisplayModule.class,
TunerModule.class,
UserModule.class,
UtilModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index 3adeeac..1f061e9 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -21,6 +21,7 @@
import static com.android.systemui.flags.FlagManager.EXTRA_FLAGS;
import static com.android.systemui.flags.FlagManager.EXTRA_ID;
import static com.android.systemui.flags.FlagManager.EXTRA_VALUE;
+import static com.android.systemui.flags.FlagsCommonModule.ALL_FLAGS;
import static java.util.Objects.requireNonNull;
@@ -59,20 +60,20 @@
*
* Flags can be set (or unset) via the following adb command:
*
- * adb shell cmd statusbar flag <id> <on|off|toggle|erase>
+ * adb shell cmd statusbar flag <id> <on|off|toggle|erase>
*
- * Alternatively, you can change flags via a broadcast intent:
+ * Alternatively, you can change flags via a broadcast intent:
*
- * adb shell am broadcast -a com.android.systemui.action.SET_FLAG --ei id <id> [--ez value <0|1>]
+ * adb shell am broadcast -a com.android.systemui.action.SET_FLAG --ei id <id> [--ez value <0|1>]
*
* To restore a flag back to its default, leave the `--ez value <0|1>` off of the command.
*/
@SysUISingleton
public class FeatureFlagsDebug implements FeatureFlags {
static final String TAG = "SysUIFlags";
- static final String ALL_FLAGS = "all_flags";
private final FlagManager mFlagManager;
+ private final Context mContext;
private final SecureSettings mSecureSettings;
private final Resources mResources;
private final SystemPropertiesHelper mSystemProperties;
@@ -83,6 +84,14 @@
private final Map<Integer, String> mStringFlagCache = new TreeMap<>();
private final Restarter mRestarter;
+ private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
+ new ServerFlagReader.ChangeListener() {
+ @Override
+ public void onChange() {
+ mRestarter.restart();
+ }
+ };
+
@Inject
public FeatureFlagsDebug(
FlagManager flagManager,
@@ -93,23 +102,28 @@
DeviceConfigProxy deviceConfigProxy,
ServerFlagReader serverFlagReader,
@Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags,
- Restarter barService) {
+ Restarter restarter) {
mFlagManager = flagManager;
+ mContext = context;
mSecureSettings = secureSettings;
mResources = resources;
mSystemProperties = systemProperties;
mDeviceConfigProxy = deviceConfigProxy;
mServerFlagReader = serverFlagReader;
mAllFlags = allFlags;
- mRestarter = barService;
+ mRestarter = restarter;
+ }
+ /** Call after construction to setup listeners. */
+ void init() {
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_SET_FLAG);
filter.addAction(ACTION_GET_FLAGS);
- flagManager.setOnSettingsChangedAction(this::restartSystemUI);
- flagManager.setClearCacheAction(this::removeFromCache);
- context.registerReceiver(mReceiver, filter, null, null,
+ mFlagManager.setOnSettingsChangedAction(this::restartSystemUI);
+ mFlagManager.setClearCacheAction(this::removeFromCache);
+ mContext.registerReceiver(mReceiver, filter, null, null,
Context.RECEIVER_EXPORTED_UNAUDITED);
+ mServerFlagReader.listenForChanges(mAllFlags.values(), mOnPropertiesChanged);
}
@Override
@@ -196,7 +210,7 @@
return mStringFlagCache.get(id);
}
- /** Specific override for Boolean flags that checks against the teamfood list.*/
+ /** Specific override for Boolean flags that checks against the teamfood list. */
private boolean readFlagValue(int id, boolean defaultValue) {
Boolean result = readBooleanFlagOverride(id);
boolean hasServerOverride = mServerFlagReader.hasOverride(id);
@@ -273,6 +287,7 @@
private void dispatchListenersAndMaybeRestart(int id, Consumer<Boolean> restartAction) {
mFlagManager.dispatchListenersAndMaybeRestart(id, restartAction);
}
+
/** Works just like {@link #eraseFlag(int)} except that it doesn't restart SystemUI. */
private void eraseInternal(int id) {
// We can't actually "erase" things from sysprops, but we can set them to empty!
@@ -358,7 +373,7 @@
}
}
- Bundle extras = getResultExtras(true);
+ Bundle extras = getResultExtras(true);
if (extras != null) {
extras.putParcelableArrayList(EXTRA_FLAGS, pFlags);
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
index 560dcbd..6271334 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
@@ -31,7 +31,7 @@
dumpManager: DumpManager,
private val commandRegistry: CommandRegistry,
private val flagCommand: FlagCommand,
- featureFlags: FeatureFlags
+ private val featureFlags: FeatureFlagsDebug
) : CoreStartable {
init {
@@ -41,6 +41,7 @@
}
override fun start() {
+ featureFlags.init()
commandRegistry.registerCommand(FlagCommand.FLAG_COMMAND) { flagCommand }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index 40a8a1a..30cad5f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -16,6 +16,8 @@
package com.android.systemui.flags;
+import static com.android.systemui.flags.FlagsCommonModule.ALL_FLAGS;
+
import static java.util.Objects.requireNonNull;
import android.content.res.Resources;
@@ -34,6 +36,7 @@
import java.util.Map;
import javax.inject.Inject;
+import javax.inject.Named;
/**
* Default implementation of the a Flag manager that returns default values for release builds
@@ -49,26 +52,47 @@
private final SystemPropertiesHelper mSystemProperties;
private final DeviceConfigProxy mDeviceConfigProxy;
private final ServerFlagReader mServerFlagReader;
+ private final Restarter mRestarter;
+ private final Map<Integer, Flag<?>> mAllFlags;
SparseBooleanArray mBooleanCache = new SparseBooleanArray();
SparseArray<String> mStringCache = new SparseArray<>();
+ private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
+ new ServerFlagReader.ChangeListener() {
+ @Override
+ public void onChange() {
+ mRestarter.restart();
+ }
+ };
+
@Inject
public FeatureFlagsRelease(
@Main Resources resources,
SystemPropertiesHelper systemProperties,
DeviceConfigProxy deviceConfigProxy,
- ServerFlagReader serverFlagReader) {
+ ServerFlagReader serverFlagReader,
+ @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags,
+ Restarter restarter) {
mResources = resources;
mSystemProperties = systemProperties;
mDeviceConfigProxy = deviceConfigProxy;
mServerFlagReader = serverFlagReader;
+ mAllFlags = allFlags;
+ mRestarter = restarter;
+ }
+
+ /** Call after construction to setup listeners. */
+ void init() {
+ mServerFlagReader.listenForChanges(mAllFlags.values(), mOnPropertiesChanged);
}
@Override
- public void addListener(@NonNull Flag<?> flag, @NonNull Listener listener) {}
+ public void addListener(@NonNull Flag<?> flag, @NonNull Listener listener) {
+ }
@Override
- public void removeListener(@NonNull Listener listener) {}
+ public void removeListener(@NonNull Listener listener) {
+ }
@Override
public boolean isEnabled(@NotNull UnreleasedFlag flag) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
index 4d25431..1e93c0b7 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
@@ -16,6 +16,8 @@
package com.android.systemui.flags;
+import static com.android.systemui.flags.FlagsCommonModule.ALL_FLAGS;
+
import androidx.annotation.NonNull;
import com.android.systemui.statusbar.commandline.Command;
@@ -42,7 +44,7 @@
@Inject
FlagCommand(
FeatureFlagsDebug featureFlags,
- @Named(FeatureFlagsDebug.ALL_FLAGS) Map<Integer, Flag<?>> allFlags
+ @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags
) {
mFeatureFlags = featureFlags;
mAllFlags = allFlags;
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 4818bcc..fa49968 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -124,6 +124,10 @@
*/
@JvmField val DOZING_MIGRATION_1 = UnreleasedFlag(213, teamfood = true)
+ @JvmField val NEW_ELLIPSE_DETECTION = UnreleasedFlag(214)
+
+ @JvmField val NEW_UDFPS_OVERLAY = UnreleasedFlag(215)
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = ReleasedFlag(300)
@@ -196,7 +200,7 @@
// 802 - wallpaper rendering
// TODO(b/254512923): Tracking Bug
- @JvmField val USE_CANVAS_RENDERER = ReleasedFlag(802)
+ @JvmField val USE_CANVAS_RENDERER = UnreleasedFlag(802, teamfood = true)
// 803 - screen contents translation
// TODO(b/254513187): Tracking Bug
@@ -230,15 +234,9 @@
// 1000 - dock
val SIMULATE_DOCK_THROUGH_CHARGING = ReleasedFlag(1000)
- // TODO(b/254512444): Tracking Bug
- @JvmField val DOCK_SETUP_ENABLED = ReleasedFlag(1001)
-
// TODO(b/254512758): Tracking Bug
@JvmField val ROUNDED_BOX_RIPPLE = ReleasedFlag(1002)
- // TODO(b/254512525): Tracking Bug
- @JvmField val REFACTORED_DOCK_SETUP = ReleasedFlag(1003, teamfood = true)
-
// 1100 - windowing
@Keep
val WM_ENABLE_SHELL_TRANSITIONS =
@@ -320,7 +318,7 @@
// 1500 - chooser
// TODO(b/254512507): Tracking Bug
- val CHOOSER_UNBUNDLED = UnreleasedFlag(1500)
+ val CHOOSER_UNBUNDLED = UnreleasedFlag(1500, true)
// 1600 - accessibility
@JvmField val A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS = UnreleasedFlag(1600)
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
new file mode 100644
index 0000000..e1f4944
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 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.flags
+
+import com.android.internal.statusbar.IStatusBarService
+import dagger.Module
+import dagger.Provides
+import javax.inject.Named
+
+/** Module containing shared code for all FeatureFlag implementations. */
+@Module
+interface FlagsCommonModule {
+ companion object {
+ const val ALL_FLAGS = "all_flags"
+
+ @JvmStatic
+ @Provides
+ @Named(ALL_FLAGS)
+ fun providesAllFlags(): Map<Int, Flag<*>> {
+ return Flags.collectFlags()
+ }
+
+ @JvmStatic
+ @Provides
+ fun providesRestarter(barService: IStatusBarService): Restarter {
+ return object : Restarter {
+ override fun restart() {
+ barService.restart()
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
index fc5b9f4..694fa01 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
@@ -16,9 +16,13 @@
package com.android.systemui.flags
+import android.provider.DeviceConfig
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.util.DeviceConfigProxy
-import dagger.Binds
import dagger.Module
+import dagger.Provides
+import java.util.concurrent.Executor
import javax.inject.Inject
interface ServerFlagReader {
@@ -27,40 +31,99 @@
/** Returns any stored server-side setting or the default if not set. */
fun readServerOverride(flagId: Int, default: Boolean): Boolean
+
+ /** Register a listener for changes to any of the passed in flags. */
+ fun listenForChanges(values: Collection<Flag<*>>, listener: ChangeListener)
+
+ interface ChangeListener {
+ fun onChange()
+ }
}
class ServerFlagReaderImpl @Inject constructor(
- private val deviceConfig: DeviceConfigProxy
+ private val namespace: String,
+ private val deviceConfig: DeviceConfigProxy,
+ @Background private val executor: Executor
) : ServerFlagReader {
+
+ private val listeners =
+ mutableListOf<Pair<ServerFlagReader.ChangeListener, Collection<Flag<*>>>>()
+
+ private val onPropertiesChangedListener = object : DeviceConfig.OnPropertiesChangedListener {
+ override fun onPropertiesChanged(properties: DeviceConfig.Properties) {
+ if (properties.namespace != namespace) {
+ return
+ }
+
+ for ((listener, flags) in listeners) {
+ propLoop@ for (propName in properties.keyset) {
+ for (flag in flags) {
+ if (propName == getServerOverrideName(flag.id)) {
+ listener.onChange()
+ break@propLoop
+ }
+ }
+ }
+ }
+ }
+ }
+
override fun hasOverride(flagId: Int): Boolean =
deviceConfig.getProperty(
- SYSUI_NAMESPACE,
+ namespace,
getServerOverrideName(flagId)
) != null
override fun readServerOverride(flagId: Int, default: Boolean): Boolean {
return deviceConfig.getBoolean(
- SYSUI_NAMESPACE,
+ namespace,
getServerOverrideName(flagId),
default
)
}
+ override fun listenForChanges(
+ flags: Collection<Flag<*>>,
+ listener: ServerFlagReader.ChangeListener
+ ) {
+ if (listeners.isEmpty()) {
+ deviceConfig.addOnPropertiesChangedListener(
+ namespace,
+ executor,
+ onPropertiesChangedListener
+ )
+ }
+ listeners.add(Pair(listener, flags))
+ }
+
private fun getServerOverrideName(flagId: Int): String {
return "flag_override_$flagId"
}
}
-private val SYSUI_NAMESPACE = "systemui"
-
@Module
interface ServerFlagReaderModule {
- @Binds
- fun bindsReader(impl: ServerFlagReaderImpl): ServerFlagReader
+ companion object {
+ private val SYSUI_NAMESPACE = "systemui"
+
+ @JvmStatic
+ @Provides
+ @SysUISingleton
+ fun bindsReader(
+ deviceConfig: DeviceConfigProxy,
+ @Background executor: Executor
+ ): ServerFlagReader {
+ return ServerFlagReaderImpl(
+ SYSUI_NAMESPACE, deviceConfig, executor
+ )
+ }
+ }
}
class ServerFlagReaderFake : ServerFlagReader {
private val flagMap: MutableMap<Int, Boolean> = mutableMapOf()
+ private val listeners =
+ mutableListOf<Pair<ServerFlagReader.ChangeListener, Collection<Flag<*>>>>()
override fun hasOverride(flagId: Int): Boolean {
return flagMap.containsKey(flagId)
@@ -72,9 +135,24 @@
fun setFlagValue(flagId: Int, value: Boolean) {
flagMap.put(flagId, value)
+
+ for ((listener, flags) in listeners) {
+ flagLoop@ for (flag in flags) {
+ if (flagId == flag.id) {
+ listener.onChange()
+ break@flagLoop
+ }
+ }
+ }
}
fun eraseFlag(flagId: Int) {
flagMap.remove(flagId)
}
+
+ override fun listenForChanges(
+ flags: Collection<Flag<*>>,
+ listener: ServerFlagReader.ChangeListener
+ ) {
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
new file mode 100644
index 0000000..a069582
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+/**
+ * Unique identifier keys for all known built-in quick affordances.
+ *
+ * Please ensure uniqueness by never associating more than one class with each key.
+ */
+object BuiltInKeyguardQuickAffordanceKeys {
+ // Please keep alphabetical order of const names to simplify future maintenance.
+ const val HOME_CONTROLS = "home"
+ const val QR_CODE_SCANNER = "qr_code_scanner"
+ const val QUICK_ACCESS_WALLET = "wallet"
+ // Please keep alphabetical order of const names to simplify future maintenance.
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
similarity index 75%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index 8384260..c600e13 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -1,21 +1,21 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2022 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
+ * 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
+ * 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.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*
*/
-package com.android.systemui.keyguard.domain.quickaffordance
+package com.android.systemui.keyguard.data.quickaffordance
import android.content.Context
import android.content.Intent
@@ -51,19 +51,21 @@
private val appContext = context.applicationContext
- override val state: Flow<KeyguardQuickAffordanceConfig.State> =
+ override val key: String = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
+
+ override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
component.canShowWhileLockedSetting.flatMapLatest { canShowWhileLocked ->
if (canShowWhileLocked) {
stateInternal(component.getControlsListingController().getOrNull())
} else {
- flowOf(KeyguardQuickAffordanceConfig.State.Hidden)
+ flowOf(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
}
}
- override fun onQuickAffordanceClicked(
+ override fun onTriggered(
expandable: Expandable?,
- ): KeyguardQuickAffordanceConfig.OnClickedResult {
- return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+ ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
intent =
Intent(appContext, ControlsActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
@@ -77,9 +79,9 @@
private fun stateInternal(
listingController: ControlsListingController?,
- ): Flow<KeyguardQuickAffordanceConfig.State> {
+ ): Flow<KeyguardQuickAffordanceConfig.LockScreenState> {
if (listingController == null) {
- return flowOf(KeyguardQuickAffordanceConfig.State.Hidden)
+ return flowOf(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
}
return conflatedCallbackFlow {
@@ -114,7 +116,7 @@
hasServiceInfos: Boolean,
visibility: ControlsComponent.Visibility,
@DrawableRes iconResourceId: Int?,
- ): KeyguardQuickAffordanceConfig.State {
+ ): KeyguardQuickAffordanceConfig.LockScreenState {
return if (
isFeatureEnabled &&
hasFavorites &&
@@ -122,7 +124,7 @@
iconResourceId != null &&
visibility == ControlsComponent.Visibility.AVAILABLE
) {
- KeyguardQuickAffordanceConfig.State.Visible(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon =
Icon.Resource(
res = iconResourceId,
@@ -133,7 +135,7 @@
),
)
} else {
- KeyguardQuickAffordanceConfig.State.Hidden
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
new file mode 100644
index 0000000..0a8090b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.Intent
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import kotlinx.coroutines.flow.Flow
+
+/** Defines interface that can act as data source for a single quick affordance model. */
+interface KeyguardQuickAffordanceConfig {
+
+ /** Unique identifier for this quick affordance. It must be globally unique. */
+ val key: String
+
+ /**
+ * The ever-changing state of the affordance.
+ *
+ * Used to populate the lock screen.
+ */
+ val lockScreenState: Flow<LockScreenState>
+
+ /**
+ * Notifies that the affordance was clicked by the user.
+ *
+ * @param expandable An [Expandable] to use when animating dialogs or activities
+ * @return An [OnTriggeredResult] telling the caller what to do next
+ */
+ fun onTriggered(expandable: Expandable?): OnTriggeredResult
+
+ /**
+ * Encapsulates the state of a "quick affordance" in the keyguard bottom area (for example, a
+ * button on the lock-screen).
+ */
+ sealed class LockScreenState {
+
+ /** No affordance should show up. */
+ object Hidden : LockScreenState()
+
+ /** An affordance is visible. */
+ data class Visible(
+ /** An icon for the affordance. */
+ val icon: Icon,
+ /** The activation state of the affordance. */
+ val activationState: ActivationState = ActivationState.NotSupported,
+ ) : LockScreenState()
+ }
+
+ sealed class OnTriggeredResult {
+ /**
+ * Returning this as a result from the [onTriggered] method means that the implementation
+ * has taken care of the action, the system will do nothing.
+ */
+ object Handled : OnTriggeredResult()
+
+ /**
+ * Returning this as a result from the [onTriggered] method means that the implementation
+ * has _not_ taken care of the action and the system should start an activity using the
+ * given [Intent].
+ */
+ data class StartActivity(
+ val intent: Intent,
+ val canShowWhileLocked: Boolean,
+ ) : OnTriggeredResult()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
new file mode 100644
index 0000000..d620b2a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** QR code scanner quick affordance data source. */
+@SysUISingleton
+class QrCodeScannerKeyguardQuickAffordanceConfig
+@Inject
+constructor(
+ private val controller: QRCodeScannerController,
+) : KeyguardQuickAffordanceConfig {
+
+ override val key: String = BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
+
+ override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
+ conflatedCallbackFlow {
+ val callback =
+ object : QRCodeScannerController.Callback {
+ override fun onQRCodeScannerActivityChanged() {
+ trySendWithFailureLogging(state(), TAG)
+ }
+ override fun onQRCodeScannerPreferenceChanged() {
+ trySendWithFailureLogging(state(), TAG)
+ }
+ }
+
+ controller.addCallback(callback)
+ controller.registerQRCodeScannerChangeObservers(
+ QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
+ QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
+ )
+ // Registering does not push an initial update.
+ trySendWithFailureLogging(state(), "initial state", TAG)
+
+ awaitClose {
+ controller.unregisterQRCodeScannerChangeObservers(
+ QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
+ QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
+ )
+ controller.removeCallback(callback)
+ }
+ }
+
+ override fun onTriggered(
+ expandable: Expandable?,
+ ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
+ intent = controller.intent,
+ canShowWhileLocked = true,
+ )
+ }
+
+ private fun state(): KeyguardQuickAffordanceConfig.LockScreenState {
+ return if (controller.isEnabledForLockScreenButton) {
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon =
+ Icon.Resource(
+ res = R.drawable.ic_qr_code_scanner,
+ contentDescription =
+ ContentDescription.Resource(
+ res = R.string.accessibility_qr_code_scanner_button,
+ ),
+ ),
+ )
+ } else {
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden
+ }
+ }
+
+ companion object {
+ private const val TAG = "QrCodeScannerKeyguardQuickAffordanceConfig"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
new file mode 100644
index 0000000..be57a32
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.graphics.drawable.Drawable
+import android.service.quickaccesswallet.GetWalletCardsError
+import android.service.quickaccesswallet.GetWalletCardsResponse
+import android.service.quickaccesswallet.QuickAccessWalletClient
+import android.util.Log
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.wallet.controller.QuickAccessWalletController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** Quick access wallet quick affordance data source. */
+@SysUISingleton
+class QuickAccessWalletKeyguardQuickAffordanceConfig
+@Inject
+constructor(
+ private val walletController: QuickAccessWalletController,
+ private val activityStarter: ActivityStarter,
+) : KeyguardQuickAffordanceConfig {
+
+ override val key: String = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+
+ override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
+ conflatedCallbackFlow {
+ val callback =
+ object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
+ override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
+ trySendWithFailureLogging(
+ state(
+ isFeatureEnabled = walletController.isWalletEnabled,
+ hasCard = response?.walletCards?.isNotEmpty() == true,
+ tileIcon = walletController.walletClient.tileIcon,
+ ),
+ TAG,
+ )
+ }
+
+ override fun onWalletCardRetrievalError(error: GetWalletCardsError?) {
+ Log.e(TAG, "Wallet card retrieval error, message: \"${error?.message}\"")
+ trySendWithFailureLogging(
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden,
+ TAG,
+ )
+ }
+ }
+
+ walletController.setupWalletChangeObservers(
+ callback,
+ QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
+ QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+ )
+ walletController.updateWalletPreference()
+ walletController.queryWalletCards(callback)
+
+ awaitClose {
+ walletController.unregisterWalletChangeObservers(
+ QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
+ QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+ )
+ }
+ }
+
+ override fun onTriggered(
+ expandable: Expandable?,
+ ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+ walletController.startQuickAccessUiIntent(
+ activityStarter,
+ expandable?.activityLaunchController(),
+ /* hasCard= */ true,
+ )
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
+
+ private fun state(
+ isFeatureEnabled: Boolean,
+ hasCard: Boolean,
+ tileIcon: Drawable?,
+ ): KeyguardQuickAffordanceConfig.LockScreenState {
+ return if (isFeatureEnabled && hasCard && tileIcon != null) {
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon =
+ Icon.Loaded(
+ drawable = tileIcon,
+ contentDescription =
+ ContentDescription.Resource(
+ res = R.string.accessibility_wallet_button,
+ ),
+ ),
+ )
+ } else {
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden
+ }
+ }
+
+ companion object {
+ private const val TAG = "QuickAccessWalletKeyguardQuickAffordanceConfig"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index f663b0d..13d97aa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -21,15 +21,14 @@
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
-import kotlin.reflect.KClass
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.onStart
@@ -63,25 +62,25 @@
}
/**
- * Notifies that a quick affordance has been clicked by the user.
+ * Notifies that a quick affordance has been "triggered" (clicked) by the user.
*
* @param configKey The configuration key corresponding to the [KeyguardQuickAffordanceModel] of
* the affordance that was clicked
* @param expandable An optional [Expandable] for the activity- or dialog-launch animation
*/
- fun onQuickAffordanceClicked(
- configKey: KClass<out KeyguardQuickAffordanceConfig>,
+ fun onQuickAffordanceTriggered(
+ configKey: String,
expandable: Expandable?,
) {
- @Suppress("UNCHECKED_CAST") val config = registry.get(configKey as KClass<Nothing>)
- when (val result = config.onQuickAffordanceClicked(expandable)) {
- is KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity ->
+ @Suppress("UNCHECKED_CAST") val config = registry.get(configKey)
+ when (val result = config.onTriggered(expandable)) {
+ is KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity ->
launchQuickAffordance(
intent = result.intent,
canShowWhileLocked = result.canShowWhileLocked,
expandable = expandable,
)
- is KeyguardQuickAffordanceConfig.OnClickedResult.Handled -> Unit
+ is KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled -> Unit
}
}
@@ -95,16 +94,20 @@
// value and avoid subtle bugs where the downstream isn't receiving any values
// because one config implementation is not emitting an initial value. For example,
// see b/244296596.
- config.state.onStart { emit(KeyguardQuickAffordanceConfig.State.Hidden) }
+ config.lockScreenState.onStart {
+ emit(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+ }
}
) { states ->
- val index = states.indexOfFirst { it is KeyguardQuickAffordanceConfig.State.Visible }
+ val index =
+ states.indexOfFirst { it is KeyguardQuickAffordanceConfig.LockScreenState.Visible }
if (index != -1) {
- val visibleState = states[index] as KeyguardQuickAffordanceConfig.State.Visible
+ val visibleState =
+ states[index] as KeyguardQuickAffordanceConfig.LockScreenState.Visible
KeyguardQuickAffordanceModel.Visible(
- configKey = configs[index]::class,
+ configKey = configs[index].key,
icon = visibleState.icon,
- toggle = visibleState.toggle,
+ activationState = visibleState.activationState,
)
} else {
KeyguardQuickAffordanceModel.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
index e56b259..32560af 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
@@ -18,9 +18,7 @@
package com.android.systemui.keyguard.domain.model
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
-import kotlin.reflect.KClass
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
/**
* Models a "quick affordance" in the keyguard bottom area (for example, a button on the
@@ -33,10 +31,10 @@
/** A affordance is visible. */
data class Visible(
/** Identifier for the affordance this is modeling. */
- val configKey: KClass<out KeyguardQuickAffordanceConfig>,
+ val configKey: String,
/** An icon for the affordance. */
val icon: Icon,
- /** The toggle state for the affordance. */
- val toggle: KeyguardQuickAffordanceToggleState,
+ /** The activation state of the affordance. */
+ val activationState: ActivationState,
) : KeyguardQuickAffordanceModel()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
deleted file mode 100644
index 95027d0..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.domain.quickaffordance
-
-import android.content.Intent
-import com.android.systemui.animation.Expandable
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
-import kotlinx.coroutines.flow.Flow
-
-/** Defines interface that can act as data source for a single quick affordance model. */
-interface KeyguardQuickAffordanceConfig {
-
- val state: Flow<State>
-
- fun onQuickAffordanceClicked(expandable: Expandable?): OnClickedResult
-
- /**
- * Encapsulates the state of a "quick affordance" in the keyguard bottom area (for example, a
- * button on the lock-screen).
- */
- sealed class State {
-
- /** No affordance should show up. */
- object Hidden : State()
-
- /** An affordance is visible. */
- data class Visible(
- /** An icon for the affordance. */
- val icon: Icon,
- /** The toggle state for the affordance. */
- val toggle: KeyguardQuickAffordanceToggleState =
- KeyguardQuickAffordanceToggleState.NotSupported,
- ) : State()
- }
-
- sealed class OnClickedResult {
- /**
- * Returning this as a result from the [onQuickAffordanceClicked] method means that the
- * implementation has taken care of the click, the system will do nothing.
- */
- object Handled : OnClickedResult()
-
- /**
- * Returning this as a result from the [onQuickAffordanceClicked] method means that the
- * implementation has _not_ taken care of the click and the system should start an activity
- * using the given [Intent].
- */
- data class StartActivity(
- val intent: Intent,
- val canShowWhileLocked: Boolean,
- ) : OnClickedResult()
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
index 94024d4..b48acb6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.quickaffordance
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import dagger.Binds
import dagger.Module
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
index ad40ee7..8526ada 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
@@ -17,14 +17,17 @@
package com.android.systemui.keyguard.domain.quickaffordance
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.QrCodeScannerKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.QuickAccessWalletKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import javax.inject.Inject
-import kotlin.reflect.KClass
/** Central registry of all known quick affordance configs. */
interface KeyguardQuickAffordanceRegistry<T : KeyguardQuickAffordanceConfig> {
fun getAll(position: KeyguardQuickAffordancePosition): List<T>
- fun get(configClass: KClass<out T>): T
+ fun get(key: String): T
}
class KeyguardQuickAffordanceRegistryImpl
@@ -46,8 +49,8 @@
qrCodeScanner,
),
)
- private val configByClass =
- configsByPosition.values.flatten().associateBy { config -> config::class }
+ private val configByKey =
+ configsByPosition.values.flatten().associateBy { config -> config.key }
override fun getAll(
position: KeyguardQuickAffordancePosition,
@@ -56,8 +59,8 @@
}
override fun get(
- configClass: KClass<out KeyguardQuickAffordanceConfig>
+ key: String,
): KeyguardQuickAffordanceConfig {
- return configByClass.getValue(configClass)
+ return configByKey.getValue(key)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
deleted file mode 100644
index 502a607..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.domain.quickaffordance
-
-import com.android.systemui.R
-import com.android.systemui.animation.Expandable
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.common.shared.model.ContentDescription
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
-import javax.inject.Inject
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-
-/** QR code scanner quick affordance data source. */
-@SysUISingleton
-class QrCodeScannerKeyguardQuickAffordanceConfig
-@Inject
-constructor(
- private val controller: QRCodeScannerController,
-) : KeyguardQuickAffordanceConfig {
-
- override val state: Flow<KeyguardQuickAffordanceConfig.State> = conflatedCallbackFlow {
- val callback =
- object : QRCodeScannerController.Callback {
- override fun onQRCodeScannerActivityChanged() {
- trySendWithFailureLogging(state(), TAG)
- }
- override fun onQRCodeScannerPreferenceChanged() {
- trySendWithFailureLogging(state(), TAG)
- }
- }
-
- controller.addCallback(callback)
- controller.registerQRCodeScannerChangeObservers(
- QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
- QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
- )
- // Registering does not push an initial update.
- trySendWithFailureLogging(state(), "initial state", TAG)
-
- awaitClose {
- controller.unregisterQRCodeScannerChangeObservers(
- QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
- QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
- )
- controller.removeCallback(callback)
- }
- }
-
- override fun onQuickAffordanceClicked(
- expandable: Expandable?,
- ): KeyguardQuickAffordanceConfig.OnClickedResult {
- return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
- intent = controller.intent,
- canShowWhileLocked = true,
- )
- }
-
- private fun state(): KeyguardQuickAffordanceConfig.State {
- return if (controller.isEnabledForLockScreenButton) {
- KeyguardQuickAffordanceConfig.State.Visible(
- icon =
- Icon.Resource(
- res = R.drawable.ic_qr_code_scanner,
- contentDescription =
- ContentDescription.Resource(
- res = R.string.accessibility_qr_code_scanner_button,
- ),
- ),
- )
- } else {
- KeyguardQuickAffordanceConfig.State.Hidden
- }
- }
-
- companion object {
- private const val TAG = "QrCodeScannerKeyguardQuickAffordanceConfig"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
deleted file mode 100644
index a24a0d6..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.domain.quickaffordance
-
-import android.graphics.drawable.Drawable
-import android.service.quickaccesswallet.GetWalletCardsError
-import android.service.quickaccesswallet.GetWalletCardsResponse
-import android.service.quickaccesswallet.QuickAccessWalletClient
-import android.util.Log
-import com.android.systemui.R
-import com.android.systemui.animation.Expandable
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.common.shared.model.ContentDescription
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.wallet.controller.QuickAccessWalletController
-import javax.inject.Inject
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-
-/** Quick access wallet quick affordance data source. */
-@SysUISingleton
-class QuickAccessWalletKeyguardQuickAffordanceConfig
-@Inject
-constructor(
- private val walletController: QuickAccessWalletController,
- private val activityStarter: ActivityStarter,
-) : KeyguardQuickAffordanceConfig {
-
- override val state: Flow<KeyguardQuickAffordanceConfig.State> = conflatedCallbackFlow {
- val callback =
- object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
- override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
- trySendWithFailureLogging(
- state(
- isFeatureEnabled = walletController.isWalletEnabled,
- hasCard = response?.walletCards?.isNotEmpty() == true,
- tileIcon = walletController.walletClient.tileIcon,
- ),
- TAG,
- )
- }
-
- override fun onWalletCardRetrievalError(error: GetWalletCardsError?) {
- Log.e(TAG, "Wallet card retrieval error, message: \"${error?.message}\"")
- trySendWithFailureLogging(
- KeyguardQuickAffordanceConfig.State.Hidden,
- TAG,
- )
- }
- }
-
- walletController.setupWalletChangeObservers(
- callback,
- QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
- QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
- )
- walletController.updateWalletPreference()
- walletController.queryWalletCards(callback)
-
- awaitClose {
- walletController.unregisterWalletChangeObservers(
- QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
- QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
- )
- }
- }
-
- override fun onQuickAffordanceClicked(
- expandable: Expandable?,
- ): KeyguardQuickAffordanceConfig.OnClickedResult {
- walletController.startQuickAccessUiIntent(
- activityStarter,
- expandable?.activityLaunchController(),
- /* hasCard= */ true,
- )
- return KeyguardQuickAffordanceConfig.OnClickedResult.Handled
- }
-
- private fun state(
- isFeatureEnabled: Boolean,
- hasCard: Boolean,
- tileIcon: Drawable?,
- ): KeyguardQuickAffordanceConfig.State {
- return if (isFeatureEnabled && hasCard && tileIcon != null) {
- KeyguardQuickAffordanceConfig.State.Visible(
- icon =
- Icon.Loaded(
- drawable = tileIcon,
- contentDescription =
- ContentDescription.Resource(
- res = R.string.accessibility_wallet_button,
- ),
- ),
- )
- } else {
- KeyguardQuickAffordanceConfig.State.Hidden
- }
- }
-
- companion object {
- private const val TAG = "QuickAccessWalletKeyguardQuickAffordanceConfig"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordanceToggleState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/ActivationState.kt
similarity index 68%
rename from packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordanceToggleState.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/ActivationState.kt
index 55d38a4..a68d190 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordanceToggleState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/ActivationState.kt
@@ -17,12 +17,12 @@
package com.android.systemui.keyguard.shared.quickaffordance
-/** Enumerates all possible toggle states for a quick affordance on the lock-screen. */
-sealed class KeyguardQuickAffordanceToggleState {
- /** Toggling is not supported. */
- object NotSupported : KeyguardQuickAffordanceToggleState()
+/** Enumerates all possible activation states for a quick affordance on the lock-screen. */
+sealed class ActivationState {
+ /** Activation is not supported. */
+ object NotSupported : ActivationState()
/** The quick affordance is on. */
- object On : KeyguardQuickAffordanceToggleState()
+ object Active : ActivationState()
/** The quick affordance is off. */
- object Off : KeyguardQuickAffordanceToggleState()
+ object Inactive : ActivationState()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt
similarity index 92%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt
index 581dafa3..a18b036 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.domain.model
+package com.android.systemui.keyguard.shared.quickaffordance
/** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */
enum class KeyguardQuickAffordancePosition {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
index 535ca72..b6b2304 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -22,8 +22,8 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -118,13 +118,13 @@
animateReveal = animateReveal,
icon = icon,
onClicked = { parameters ->
- quickAffordanceInteractor.onQuickAffordanceClicked(
+ quickAffordanceInteractor.onQuickAffordanceTriggered(
configKey = parameters.configKey,
expandable = parameters.expandable,
)
},
isClickable = isClickable,
- isActivated = toggle is KeyguardQuickAffordanceToggleState.On,
+ isActivated = activationState is ActivationState.Active,
)
is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
index bf598ba..44f48f9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
@@ -18,12 +18,10 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
-import kotlin.reflect.KClass
/** Models the UI state of a keyguard quick affordance button. */
data class KeyguardQuickAffordanceViewModel(
- val configKey: KClass<out KeyguardQuickAffordanceConfig>? = null,
+ val configKey: String? = null,
val isVisible: Boolean = false,
/** Whether to animate the transition of the quick affordance from invisible to visible. */
val animateReveal: Boolean = false,
@@ -33,7 +31,7 @@
val isActivated: Boolean = false,
) {
data class OnClickedParameters(
- val configKey: KClass<out KeyguardQuickAffordanceConfig>,
+ val configKey: String,
val expandable: Expandable?,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index 6b46d8f..cbb670e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -43,7 +43,8 @@
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.media.dream.MediaDreamComplication
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.NotifPanelEvents
+import com.android.systemui.shade.ShadeStateEvents
+import com.android.systemui.shade.ShadeStateEvents.ShadeStateEventsListener
import com.android.systemui.statusbar.CrossFadeHelper
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -96,7 +97,7 @@
private val dreamOverlayStateController: DreamOverlayStateController,
configurationController: ConfigurationController,
wakefulnessLifecycle: WakefulnessLifecycle,
- panelEventsEvents: NotifPanelEvents,
+ panelEventsEvents: ShadeStateEvents,
private val secureSettings: SecureSettings,
@Main private val handler: Handler,
) {
@@ -534,8 +535,8 @@
mediaHosts.forEach { it?.updateViewVisibility() }
}
- panelEventsEvents.registerListener(
- object : NotifPanelEvents.Listener {
+ panelEventsEvents.addShadeStateEventsListener(
+ object : ShadeStateEventsListener {
override fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {
skipQqsOnExpansion = isExpandImmediateEnabled
updateDesiredLocation()
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
index 0a60437..769494a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
@@ -27,10 +27,11 @@
/** Utility methods for media tap-to-transfer. */
class MediaTttUtils {
companion object {
- // Used in CTS tests UpdateMediaTapToTransferSenderDisplayTest and
- // UpdateMediaTapToTransferReceiverDisplayTest
- const val WINDOW_TITLE = "Media Transfer Chip View"
- const val WAKE_REASON = "MEDIA_TRANSFER_ACTIVATED"
+ const val WINDOW_TITLE_SENDER = "Media Transfer Chip View (Sender)"
+ const val WINDOW_TITLE_RECEIVER = "Media Transfer Chip View (Receiver)"
+
+ const val WAKE_REASON_SENDER = "MEDIA_TRANSFER_ACTIVATED_SENDER"
+ const val WAKE_REASON_RECEIVER = "MEDIA_TRANSFER_ACTIVATED_RECEIVER"
/**
* Returns the information needed to display the icon in [Icon] form.
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index dc794e6..7dd9fb4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -40,7 +40,6 @@
import com.android.systemui.media.taptotransfer.common.MediaTttUtils
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.temporarydisplay.DEFAULT_TIMEOUT_MILLIS
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
import com.android.systemui.temporarydisplay.TemporaryViewInfo
import com.android.systemui.util.animation.AnimationUtil.Companion.frames
@@ -78,8 +77,6 @@
configurationController,
powerManager,
R.layout.media_ttt_chip_receiver,
- MediaTttUtils.WINDOW_TITLE,
- MediaTttUtils.WAKE_REASON,
) {
@SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
override val windowLayoutParams = commonWindowLayoutParams.apply {
@@ -231,7 +228,7 @@
data class ChipReceiverInfo(
val routeInfo: MediaRoute2Info,
val appIconDrawableOverride: Drawable?,
- val appNameOverride: CharSequence?
-) : TemporaryViewInfo {
- override fun getTimeoutMs() = DEFAULT_TIMEOUT_MILLIS
-}
+ val appNameOverride: CharSequence?,
+ override val windowTitle: String = MediaTttUtils.WINDOW_TITLE_RECEIVER,
+ override val wakeReason: String = MediaTttUtils.WAKE_REASON_RECEIVER,
+) : TemporaryViewInfo()
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index 6e596ee..af7317c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -43,7 +43,7 @@
@StringRes val stringResId: Int?,
val transferStatus: TransferStatus,
val endItem: SenderEndItem?,
- val timeout: Long = DEFAULT_TIMEOUT_MILLIS
+ val timeout: Int = DEFAULT_TIMEOUT_MILLIS,
) {
/**
* A state representing that the two devices are close but not close enough to *start* a cast to
@@ -223,6 +223,6 @@
// Give the Transfer*Triggered states a longer timeout since those states represent an active
// process and we should keep the user informed about it as long as possible (but don't allow it to
// continue indefinitely).
-private const val TRANSFER_TRIGGERED_TIMEOUT_MILLIS = 30000L
+private const val TRANSFER_TRIGGERED_TIMEOUT_MILLIS = 30000
private const val TAG = "ChipStateSender"
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index 1fa8fae..d1ea2d0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -159,6 +159,9 @@
}
},
vibrationEffect = chipStateSender.transferStatus.vibrationEffect,
+ windowTitle = MediaTttUtils.WINDOW_TITLE_SENDER,
+ wakeReason = MediaTttUtils.WAKE_REASON_SENDER,
+ timeoutMs = chipStateSender.timeout,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index b39175e..1c0f057 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -135,7 +135,6 @@
import com.android.systemui.camera.CameraGestureHelper;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingCollector;
-import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeLog;
@@ -231,7 +230,6 @@
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.util.Compile;
import com.android.systemui.util.LargeScreenUtils;
-import com.android.systemui.util.ListenerSet;
import com.android.systemui.util.Utils;
import com.android.systemui.util.time.SystemClock;
import com.android.wm.shell.animation.FlingAnimationUtils;
@@ -372,7 +370,6 @@
private final TapAgainViewController mTapAgainViewController;
private final LargeScreenShadeHeaderController mLargeScreenShadeHeaderController;
private final RecordingController mRecordingController;
- private final PanelEventsEmitter mPanelEventsEmitter;
private final boolean mVibrateOnOpening;
private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
private final FlingAnimationUtils mFlingAnimationUtilsClosing;
@@ -880,7 +877,6 @@
Provider<KeyguardBottomAreaViewController> keyguardBottomAreaViewControllerProvider,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
NotificationListContainer notificationListContainer,
- PanelEventsEmitter panelEventsEmitter,
NotificationStackSizeCalculator notificationStackSizeCalculator,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
ShadeTransitionController shadeTransitionController,
@@ -993,7 +989,6 @@
mMediaDataManager = mediaDataManager;
mTapAgainViewController = tapAgainViewController;
mSysUiState = sysUiState;
- mPanelEventsEmitter = panelEventsEmitter;
pulseExpansionHandler.setPulseExpandAbortListener(() -> {
if (mQs != null) {
mQs.animateHeaderSlidingOut();
@@ -1948,7 +1943,7 @@
private void setQsExpandImmediate(boolean expandImmediate) {
if (expandImmediate != mQsExpandImmediate) {
mQsExpandImmediate = expandImmediate;
- mPanelEventsEmitter.notifyExpandImmediateChange(expandImmediate);
+ mShadeExpansionStateManager.notifyExpandImmediateChange(expandImmediate);
}
}
@@ -2674,8 +2669,8 @@
// When expanding QS, let's authenticate the user if possible,
// this will speed up notification actions.
- if (height == 0) {
- mCentralSurfaces.requestFaceAuth(false, FaceAuthApiRequestReason.QS_EXPANDED);
+ if (height == 0 && !mKeyguardStateController.canDismissLockScreen()) {
+ mUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.QS_EXPANDED);
}
}
@@ -3889,7 +3884,7 @@
boolean wasRunning = mIsLaunchAnimationRunning;
mIsLaunchAnimationRunning = running;
if (wasRunning != mIsLaunchAnimationRunning) {
- mPanelEventsEmitter.notifyLaunchingActivityChanged(running);
+ mShadeExpansionStateManager.notifyLaunchingActivityChanged(running);
}
}
@@ -3898,7 +3893,7 @@
boolean wasClosing = isClosing();
mClosing = isClosing;
if (wasClosing != isClosing) {
- mPanelEventsEmitter.notifyPanelCollapsingChanged(isClosing);
+ mShadeExpansionStateManager.notifyPanelCollapsingChanged(isClosing);
}
mAmbientState.setIsClosing(isClosing);
}
@@ -3933,7 +3928,7 @@
mShadeLog.v("onMiddleClicked on Keyguard, mDozingOnDown: false");
// Try triggering face auth, this "might" run. Check
// KeyguardUpdateMonitor#shouldListenForFace to see when face auth won't run.
- boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(true,
+ boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(
FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED);
if (didFaceAuthRun) {
@@ -4223,8 +4218,8 @@
/**
* Sets the dozing state.
*
- * @param dozing {@code true} when dozing.
- * @param animate if transition should be animated.
+ * @param dozing {@code true} when dozing.
+ * @param animate if transition should be animated.
*/
public void setDozing(boolean dozing, boolean animate) {
if (dozing == mDozing) return;
@@ -4364,35 +4359,35 @@
/**
* Starts fold to AOD animation.
*
- * @param startAction invoked when the animation starts.
- * @param endAction invoked when the animation finishes, also if it was cancelled.
+ * @param startAction invoked when the animation starts.
+ * @param endAction invoked when the animation finishes, also if it was cancelled.
* @param cancelAction invoked when the animation is cancelled, before endAction.
*/
public void startFoldToAodAnimation(Runnable startAction, Runnable endAction,
Runnable cancelAction) {
mView.animate()
- .translationX(0)
- .alpha(1f)
- .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
- .setInterpolator(EMPHASIZED_DECELERATE)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- startAction.run();
- }
+ .translationX(0)
+ .alpha(1f)
+ .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
+ .setInterpolator(EMPHASIZED_DECELERATE)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ startAction.run();
+ }
- @Override
- public void onAnimationCancel(Animator animation) {
- cancelAction.run();
- }
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ cancelAction.run();
+ }
- @Override
- public void onAnimationEnd(Animator animation) {
- endAction.run();
- }
- }).setUpdateListener(anim -> {
- mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
- }).start();
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ endAction.run();
+ }
+ }).setUpdateListener(anim -> {
+ mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
+ }).start();
}
/**
@@ -4752,8 +4747,10 @@
/**
* Maybe vibrate as panel is opened.
*
- * @param openingWithTouch Whether the panel is being opened with touch. If the panel is instead
- * being opened programmatically (such as by the open panel gesture), we always play haptic.
+ * @param openingWithTouch Whether the panel is being opened with touch. If the panel is
+ * instead
+ * being opened programmatically (such as by the open panel gesture), we
+ * always play haptic.
*/
private void maybeVibrateOnOpening(boolean openingWithTouch) {
if (mVibrateOnOpening) {
@@ -4919,10 +4916,12 @@
animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
animator.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled;
+
@Override
public void onAnimationCancel(Animator animation) {
mCancelled = true;
}
+
@Override
public void onAnimationEnd(Animator animation) {
mIsSpringBackAnimation = false;
@@ -4970,7 +4969,7 @@
if (isNaN(h)) {
Log.wtf(TAG, "ExpandedHeight set to NaN");
}
- mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
+ mNotificationShadeWindowController.batchApplyWindowLayoutParams(() -> {
if (mExpandLatencyTracking && h != 0f) {
DejankUtils.postAfterTraversal(
() -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL));
@@ -5161,7 +5160,7 @@
/**
* Create an animator that can also overshoot
*
- * @param targetHeight the target height
+ * @param targetHeight the target height
* @param overshootAmount the amount of overshoot desired
*/
private ValueAnimator createHeightAnimator(float targetHeight, float overshootAmount) {
@@ -5917,49 +5916,11 @@
}
}
- @SysUISingleton
- static class PanelEventsEmitter implements NotifPanelEvents {
-
- private final ListenerSet<Listener> mListeners = new ListenerSet<>();
-
- @Inject
- PanelEventsEmitter() {
- }
-
- @Override
- public void registerListener(@androidx.annotation.NonNull @NonNull Listener listener) {
- mListeners.addIfAbsent(listener);
- }
-
- @Override
- public void unregisterListener(@androidx.annotation.NonNull @NonNull Listener listener) {
- mListeners.remove(listener);
- }
-
- private void notifyLaunchingActivityChanged(boolean isLaunchingActivity) {
- for (Listener cb : mListeners) {
- cb.onLaunchingActivityChanged(isLaunchingActivity);
- }
- }
-
- private void notifyPanelCollapsingChanged(boolean isCollapsing) {
- for (NotifPanelEvents.Listener cb : mListeners) {
- cb.onPanelCollapsingChanged(isCollapsing);
- }
- }
-
- private void notifyExpandImmediateChange(boolean expandImmediateEnabled) {
- for (NotifPanelEvents.Listener cb : mListeners) {
- cb.onExpandImmediateChanged(expandImmediateEnabled);
- }
- }
- }
-
/** Handles MotionEvents for the Shade. */
public final class TouchHandler implements View.OnTouchListener {
private long mLastTouchDownTime = -1L;
- /** @see ViewGroup#onInterceptTouchEvent(MotionEvent) */
+ /** @see ViewGroup#onInterceptTouchEvent(MotionEvent) */
public boolean onInterceptTouchEvent(MotionEvent event) {
if (SPEW_LOGCAT) {
Log.v(TAG,
@@ -6158,7 +6119,7 @@
mShadeLog.logMotionEvent(event, "onTouch: touch ignored due to instant expanding");
return false;
}
- if (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL) {
+ if (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL) {
mShadeLog.logMotionEvent(event, "onTouch: non-cancel action, touch disabled");
return false;
}
@@ -6366,3 +6327,4 @@
}
}
}
+
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeEventsModule.java
similarity index 77%
rename from packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java
rename to packages/SystemUI/src/com/android/systemui/shade/ShadeEventsModule.java
index 6772384..959c339 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeEventsModule.java
@@ -21,10 +21,9 @@
import dagger.Binds;
import dagger.Module;
-/** Provides a {@link NotifPanelEvents} in {@link SysUISingleton} scope. */
+/** Provides a {@link ShadeStateEvents} in {@link SysUISingleton} scope. */
@Module
-public abstract class NotifPanelEventsModule {
+public abstract class ShadeEventsModule {
@Binds
- abstract NotifPanelEvents bindPanelEvents(
- NotificationPanelViewController.PanelEventsEmitter impl);
+ abstract ShadeStateEvents bindShadeEvents(ShadeExpansionStateManager impl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index 7bba74a..667392c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -20,6 +20,7 @@
import android.util.Log
import androidx.annotation.FloatRange
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.ShadeStateEvents.ShadeStateEventsListener
import com.android.systemui.util.Compile
import java.util.concurrent.CopyOnWriteArrayList
import javax.inject.Inject
@@ -30,11 +31,12 @@
* TODO(b/200063118): Make this class the one source of truth for the state of panel expansion.
*/
@SysUISingleton
-class ShadeExpansionStateManager @Inject constructor() {
+class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents {
private val expansionListeners = CopyOnWriteArrayList<ShadeExpansionListener>()
private val qsExpansionListeners = CopyOnWriteArrayList<ShadeQsExpansionListener>()
private val stateListeners = CopyOnWriteArrayList<ShadeStateListener>()
+ private val shadeStateEventsListeners = CopyOnWriteArrayList<ShadeStateEventsListener>()
@PanelState private var state: Int = STATE_CLOSED
@FloatRange(from = 0.0, to = 1.0) private var fraction: Float = 0f
@@ -79,6 +81,14 @@
stateListeners.remove(listener)
}
+ override fun addShadeStateEventsListener(listener: ShadeStateEventsListener) {
+ shadeStateEventsListeners.addIfAbsent(listener)
+ }
+
+ override fun removeShadeStateEventsListener(listener: ShadeStateEventsListener) {
+ shadeStateEventsListeners.remove(listener)
+ }
+
/** Returns true if the panel is currently closed and false otherwise. */
fun isClosed(): Boolean = state == STATE_CLOSED
@@ -162,6 +172,24 @@
stateListeners.forEach { it.onPanelStateChanged(state) }
}
+ fun notifyLaunchingActivityChanged(isLaunchingActivity: Boolean) {
+ for (cb in shadeStateEventsListeners) {
+ cb.onLaunchingActivityChanged(isLaunchingActivity)
+ }
+ }
+
+ fun notifyPanelCollapsingChanged(isCollapsing: Boolean) {
+ for (cb in shadeStateEventsListeners) {
+ cb.onPanelCollapsingChanged(isCollapsing)
+ }
+ }
+
+ fun notifyExpandImmediateChange(expandImmediateEnabled: Boolean) {
+ for (cb in shadeStateEventsListeners) {
+ cb.onExpandImmediateChanged(expandImmediateEnabled)
+ }
+ }
+
private fun debugLog(msg: String) {
if (!DEBUG) return
Log.v(TAG, msg)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt
similarity index 70%
rename from packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt
rename to packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt
index 4558061..56bb1a6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt
@@ -16,27 +16,25 @@
package com.android.systemui.shade
-/** Provides certain notification panel events. */
-interface NotifPanelEvents {
+/** Provides certain notification panel events. */
+interface ShadeStateEvents {
- /** Registers callbacks to be invoked when notification panel events occur. */
- fun registerListener(listener: Listener)
+ /** Registers callbacks to be invoked when notification panel events occur. */
+ fun addShadeStateEventsListener(listener: ShadeStateEventsListener)
- /** Unregisters callbacks previously registered via [registerListener] */
- fun unregisterListener(listener: Listener)
+ /** Unregisters callbacks previously registered via [addShadeStateEventsListener] */
+ fun removeShadeStateEventsListener(listener: ShadeStateEventsListener)
/** Callbacks for certain notification panel events. */
- interface Listener {
+ interface ShadeStateEventsListener {
/** Invoked when the notification panel starts or stops collapsing. */
- @JvmDefault
- fun onPanelCollapsingChanged(isCollapsing: Boolean) {}
+ @JvmDefault fun onPanelCollapsingChanged(isCollapsing: Boolean) {}
/**
* Invoked when the notification panel starts or stops launching an [android.app.Activity].
*/
- @JvmDefault
- fun onLaunchingActivityChanged(isLaunchingActivity: Boolean) {}
+ @JvmDefault fun onLaunchingActivityChanged(isLaunchingActivity: Boolean) {}
/**
* Invoked when the "expand immediate" attribute changes.
@@ -47,7 +45,6 @@
* Another example is when full QS is showing, and we swipe up from the bottom. Instead of
* going to QQS, the panel fully collapses.
*/
- @JvmDefault
- fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {}
+ @JvmDefault fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index d3bc257..3002a68 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -28,7 +28,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.NotifPanelEvents;
+import com.android.systemui.shade.ShadeStateEvents;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -55,12 +55,12 @@
// TODO(b/204468557): Move to @CoordinatorScope
@SysUISingleton
public class VisualStabilityCoordinator implements Coordinator, Dumpable,
- NotifPanelEvents.Listener {
+ ShadeStateEvents.ShadeStateEventsListener {
public static final String TAG = "VisualStability";
public static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
private final DelayableExecutor mDelayableExecutor;
private final HeadsUpManager mHeadsUpManager;
- private final NotifPanelEvents mNotifPanelEvents;
+ private final ShadeStateEvents mShadeStateEvents;
private final StatusBarStateController mStatusBarStateController;
private final VisualStabilityProvider mVisualStabilityProvider;
private final WakefulnessLifecycle mWakefulnessLifecycle;
@@ -92,7 +92,7 @@
DelayableExecutor delayableExecutor,
DumpManager dumpManager,
HeadsUpManager headsUpManager,
- NotifPanelEvents notifPanelEvents,
+ ShadeStateEvents shadeStateEvents,
StatusBarStateController statusBarStateController,
VisualStabilityProvider visualStabilityProvider,
WakefulnessLifecycle wakefulnessLifecycle) {
@@ -101,7 +101,7 @@
mWakefulnessLifecycle = wakefulnessLifecycle;
mStatusBarStateController = statusBarStateController;
mDelayableExecutor = delayableExecutor;
- mNotifPanelEvents = notifPanelEvents;
+ mShadeStateEvents = shadeStateEvents;
dumpManager.registerDumpable(this);
}
@@ -114,7 +114,7 @@
mStatusBarStateController.addCallback(mStatusBarStateControllerListener);
mPulsing = mStatusBarStateController.isPulsing();
- mNotifPanelEvents.registerListener(this);
+ mShadeStateEvents.addShadeStateEventsListener(this);
pipeline.setVisualStabilityManager(mNotifStabilityManager);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index da4cced..ff63891 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -32,8 +32,8 @@
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserContextProvider;
-import com.android.systemui.shade.NotifPanelEventsModule;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeEventsModule;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
@@ -93,7 +93,7 @@
@Module(includes = {
CoordinatorsModule.class,
KeyguardNotificationVisibilityProviderModule.class,
- NotifPanelEventsModule.class,
+ ShadeEventsModule.class,
NotifPipelineChoreographerModule.class,
NotificationSectionHeadersModule.class,
})
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 70cf56d..2504fc1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -41,7 +41,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.RegisterStatusBarResult;
import com.android.keyguard.AuthKeyguardMessageArea;
-import com.android.keyguard.FaceAuthApiRequestReason;
import com.android.systemui.Dumpable;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.RemoteTransitionAdapter;
@@ -230,13 +229,6 @@
boolean isShadeDisabled();
- /**
- * Request face auth to initiated
- * @param userInitiatedRequest Whether this was a user initiated request
- * @param reason Reason why face auth was triggered.
- */
- void requestFaceAuth(boolean userInitiatedRequest, @FaceAuthApiRequestReason String reason);
-
@Override
void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade,
int flags);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 43bc99f..d227ed3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -122,7 +122,6 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.RegisterStatusBarResult;
import com.android.keyguard.AuthKeyguardMessageArea;
-import com.android.keyguard.FaceAuthApiRequestReason;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.ViewMediatorCallback;
@@ -1614,18 +1613,6 @@
return (mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0;
}
- /**
- * Asks {@link KeyguardUpdateMonitor} to run face auth.
- */
- @Override
- public void requestFaceAuth(boolean userInitiatedRequest,
- @FaceAuthApiRequestReason String reason) {
- if (!mKeyguardStateController.canDismissLockScreen()) {
- mKeyguardUpdateMonitor.requestFaceAuth(
- userInitiatedRequest, reason);
- }
- }
-
private void updateReportRejectedTouchVisibility() {
if (mReportRejectedTouch == null) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index 5e26cf0..4550cb2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -73,7 +73,6 @@
isListening = false
updateListeningState()
keyguardUpdateMonitor.requestFaceAuth(
- true,
FaceAuthApiRequestReason.PICK_UP_GESTURE_TRIGGERED
)
keyguardUpdateMonitor.requestActiveUnlock(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 9767103..c189ace 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -649,37 +649,6 @@
return mNumDots > 0;
}
- /**
- * If the overflow is in the range [1, max_dots - 1) (basically 1 or 2 dots), then
- * extra padding will have to be accounted for
- *
- * This method has no meaning for non-static containers
- */
- public boolean hasPartialOverflow() {
- return mNumDots > 0 && mNumDots < MAX_DOTS;
- }
-
- /**
- * Get padding that can account for extra dots up to the max. The only valid values for
- * this method are for 1 or 2 dots.
- * @return only extraDotPadding or extraDotPadding * 2
- */
- public int getPartialOverflowExtraPadding() {
- if (!hasPartialOverflow()) {
- return 0;
- }
-
- int partialOverflowAmount = (MAX_DOTS - mNumDots) * (mStaticDotDiameter + mDotPadding);
-
- int adjustedWidth = getFinalTranslationX() + partialOverflowAmount;
- // In case we actually give too much padding...
- if (adjustedWidth > getWidth()) {
- partialOverflowAmount = getWidth() - getFinalTranslationX();
- }
-
- return partialOverflowAmount;
- }
-
// Give some extra room for btw notifications if we can
public int getNoOverflowExtraPadding() {
if (mNumDots != 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
new file mode 100644
index 0000000..da87f73
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 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.pipeline.mobile.data.model
+
+import android.telephony.TelephonyManager.DATA_CONNECTED
+import android.telephony.TelephonyManager.DATA_CONNECTING
+import android.telephony.TelephonyManager.DATA_DISCONNECTED
+import android.telephony.TelephonyManager.DATA_DISCONNECTING
+import android.telephony.TelephonyManager.DataState
+
+/** Internal enum representation of the telephony data connection states */
+enum class DataConnectionState(@DataState val dataState: Int) {
+ Connected(DATA_CONNECTED),
+ Connecting(DATA_CONNECTING),
+ Disconnected(DATA_DISCONNECTED),
+ Disconnecting(DATA_DISCONNECTING),
+}
+
+fun @receiver:DataState Int.toDataConnectionType(): DataConnectionState =
+ when (this) {
+ DATA_CONNECTED -> DataConnectionState.Connected
+ DATA_CONNECTING -> DataConnectionState.Connecting
+ DATA_DISCONNECTED -> DataConnectionState.Disconnected
+ DATA_DISCONNECTING -> DataConnectionState.Disconnecting
+ else -> throw IllegalArgumentException("unknown data state received")
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt
index eaba0e9..6341a11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt
@@ -28,6 +28,7 @@
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyManager
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected
/**
* Data class containing all of the relevant information for a particular line of service, known as
@@ -49,14 +50,14 @@
@IntRange(from = 0, to = 4)
val primaryLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
- /** Comes directly from [DataConnectionStateListener.onDataConnectionStateChanged] */
- val dataConnectionState: Int? = null,
+ /** Mapped from [DataConnectionStateListener.onDataConnectionStateChanged] */
+ val dataConnectionState: DataConnectionState = Disconnected,
/** From [DataActivityListener.onDataActivity]. See [TelephonyManager] for the values */
@DataActivityType val dataActivityDirection: Int? = null,
/** From [CarrierNetworkListener.onCarrierNetworkChange] */
- val carrierNetworkChangeActive: Boolean? = null,
+ val carrierNetworkChangeActive: Boolean = false,
/**
* From [DisplayInfoListener.onDisplayInfoChanged].
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 45284cf..06e8f46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -31,7 +31,9 @@
import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.toDataConnectionType
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
import java.lang.IllegalStateException
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -42,7 +44,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/**
@@ -62,13 +64,15 @@
* listener + model.
*/
val subscriptionModelFlow: Flow<MobileSubscriptionModel>
+ /** Observable tracking [TelephonyManager.isDataConnectionAllowed] */
+ val dataEnabled: Flow<Boolean>
}
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
class MobileConnectionRepositoryImpl(
private val subId: Int,
- telephonyManager: TelephonyManager,
+ private val telephonyManager: TelephonyManager,
bgDispatcher: CoroutineDispatcher,
logger: ConnectivityPipelineLogger,
scope: CoroutineScope,
@@ -127,7 +131,8 @@
dataState: Int,
networkType: Int
) {
- state = state.copy(dataConnectionState = dataState)
+ state =
+ state.copy(dataConnectionState = dataState.toDataConnectionType())
trySend(state)
}
@@ -160,10 +165,21 @@
telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
}
- .onEach { logger.logOutputChange("mobileSubscriptionModel", it.toString()) }
+ .logOutputChange(logger, "MobileSubscriptionModel")
.stateIn(scope, SharingStarted.WhileSubscribed(), state)
}
+ /**
+ * There are a few cases where we will need to poll [TelephonyManager] so we can update some
+ * internal state where callbacks aren't provided. Any of those events should be merged into
+ * this flow, which can be used to trigger the polling.
+ */
+ private val telephonyPollingEvent: Flow<Unit> = subscriptionModelFlow.map {}
+
+ override val dataEnabled: Flow<Boolean> = telephonyPollingEvent.map { dataConnectionAllowed() }
+
+ private fun dataConnectionAllowed(): Boolean = telephonyManager.isDataConnectionAllowed
+
class Factory
@Inject
constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 15f4acc..f99d278c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -29,6 +29,9 @@
import kotlinx.coroutines.flow.map
interface MobileIconInteractor {
+ /** Observable for the data enabled state of this connection */
+ val isDataEnabled: Flow<Boolean>
+
/** Observable for RAT type (network type) indicator */
val networkTypeIconGroup: Flow<MobileIconGroup>
@@ -54,6 +57,8 @@
) : MobileIconInteractor {
private val mobileStatusInfo = connectionRepository.subscriptionModelFlow
+ override val isDataEnabled: Flow<Boolean> = connectionRepository.dataEnabled
+
/** Observable for the current RAT indicator icon ([MobileIconGroup]) */
override val networkTypeIconGroup: Flow<MobileIconGroup> =
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index cd411a4..614d583 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -23,12 +23,14 @@
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -47,28 +49,38 @@
* icon
*/
interface MobileIconsInteractor {
+ /** List of subscriptions, potentially filtered for CBRS */
val filteredSubscriptions: Flow<List<SubscriptionInfo>>
+ /** The icon mapping from network type to [MobileIconGroup] for the default subscription */
val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>
+ /** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */
val defaultMobileIconGroup: Flow<MobileIconGroup>
+ /** True once the user has been set up */
val isUserSetup: Flow<Boolean>
+ /**
+ * Vends out a [MobileIconInteractor] tracking the [MobileConnectionRepository] for the given
+ * subId. Will throw if the ID is invalid
+ */
fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor
}
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class MobileIconsInteractorImpl
@Inject
constructor(
- private val mobileSubscriptionRepo: MobileConnectionsRepository,
+ private val mobileConnectionsRepo: MobileConnectionsRepository,
private val carrierConfigTracker: CarrierConfigTracker,
private val mobileMappingsProxy: MobileMappingsProxy,
userSetupRepo: UserSetupRepository,
@Application private val scope: CoroutineScope,
) : MobileIconsInteractor {
private val activeMobileDataSubscriptionId =
- mobileSubscriptionRepo.activeMobileDataSubscriptionId
+ mobileConnectionsRepo.activeMobileDataSubscriptionId
private val unfilteredSubscriptions: Flow<List<SubscriptionInfo>> =
- mobileSubscriptionRepo.subscriptionsFlow
+ mobileConnectionsRepo.subscriptionsFlow
/**
* Generally, SystemUI wants to show iconography for each subscription that is listed by
@@ -119,13 +131,13 @@
* subscription Id. This mapping is the same for every subscription.
*/
override val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> =
- mobileSubscriptionRepo.defaultDataSubRatConfig
+ mobileConnectionsRepo.defaultDataSubRatConfig
.map { mobileMappingsProxy.mapIconSets(it) }
.stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = mapOf())
/** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */
override val defaultMobileIconGroup: StateFlow<MobileIconGroup> =
- mobileSubscriptionRepo.defaultDataSubRatConfig
+ mobileConnectionsRepo.defaultDataSubRatConfig
.map { mobileMappingsProxy.getDefaultIcons(it) }
.stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = TelephonyIcons.G)
@@ -137,6 +149,6 @@
defaultMobileIconMapping,
defaultMobileIconGroup,
mobileMappingsProxy,
- mobileSubscriptionRepo.getRepoForSubId(subId),
+ mobileConnectionsRepo.getRepoForSubId(subId),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index cc8f6dd..8131739 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -28,7 +28,6 @@
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
/**
* View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over
@@ -59,12 +58,18 @@
/** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
var networkTypeIcon: Flow<Icon?> =
- iconInteractor.networkTypeIconGroup.map {
- val desc =
- if (it.dataContentDescription != 0)
- ContentDescription.Resource(it.dataContentDescription)
- else null
- Icon.Resource(it.dataType, desc)
+ combine(iconInteractor.networkTypeIconGroup, iconInteractor.isDataEnabled) {
+ networkTypeIconGroup,
+ isDataEnabled ->
+ if (!isDataEnabled) {
+ null
+ } else {
+ val desc =
+ if (networkTypeIconGroup.dataContentDescription != 0)
+ ContentDescription.Resource(networkTypeIconGroup.dataContentDescription)
+ else null
+ Icon.Resource(networkTypeIconGroup.dataType, desc)
+ }
}
var tint: Flow<Int> = flowOf(Color.CYAN)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt
index 118b94c7..6efb10f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt
@@ -34,7 +34,7 @@
@Inject
constructor(dumpManager: DumpManager, telephonyManager: TelephonyManager) : Dumpable {
init {
- dumpManager.registerDumpable("$SB_LOGGING_TAG:ConnectivityConstants", this)
+ dumpManager.registerDumpable("${SB_LOGGING_TAG}Constants", this)
}
/** True if this device has the capability for data connections and false otherwise. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
index 6b1750d..45c6d46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
@@ -62,7 +62,7 @@
tunerService: TunerService,
) : ConnectivityRepository, Dumpable {
init {
- dumpManager.registerDumpable("$SB_LOGGING_TAG:ConnectivityRepository", this)
+ dumpManager.registerDumpable("${SB_LOGGING_TAG}Repository", this)
}
// The default set of hidden icons to use if we don't get any from [TunerService].
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
index 0eb4b0d..3c0eb91 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
@@ -35,7 +35,7 @@
dumpManager: DumpManager,
) : Dumpable {
init {
- dumpManager.registerDumpable("$SB_LOGGING_TAG:WifiConstants", this)
+ dumpManager.registerDumpable("${SB_LOGGING_TAG}WifiConstants", this)
}
/** True if we should show the activityIn/activityOut icons and false otherwise. */
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index f0a50de..637fac0 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -44,11 +44,6 @@
*
* The generic type T is expected to contain all the information necessary for the subclasses to
* display the view in a certain state, since they receive <T> in [updateView].
- *
- * @property windowTitle the title to use for the window that displays the temporary view. Should be
- * normally cased, like "Window Title".
- * @property wakeReason a string used for logging if we needed to wake the screen in order to
- * display the temporary view. Should be screaming snake cased, like WAKE_REASON.
*/
abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : TemporaryViewLogger>(
internal val context: Context,
@@ -59,8 +54,6 @@
private val configurationController: ConfigurationController,
private val powerManager: PowerManager,
@LayoutRes private val viewLayoutRes: Int,
- private val windowTitle: String,
- private val wakeReason: String,
) : CoreStartable {
/**
* Window layout params that will be used as a starting point for the [windowLayoutParams] of
@@ -72,7 +65,6 @@
type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR
flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- title = windowTitle
format = PixelFormat.TRANSLUCENT
setTrustedOverlay()
}
@@ -100,29 +92,40 @@
fun displayView(newInfo: T) {
val currentDisplayInfo = displayInfo
- if (currentDisplayInfo != null) {
+ if (currentDisplayInfo != null &&
+ currentDisplayInfo.info.windowTitle == newInfo.windowTitle) {
+ // We're already displaying information in the correctly-titled window, so we just need
+ // to update the view.
currentDisplayInfo.info = newInfo
updateView(currentDisplayInfo.info, currentDisplayInfo.view)
} else {
- // The view is new, so set up all our callbacks and inflate the view
+ if (currentDisplayInfo != null) {
+ // We're already displaying information but that information is under a different
+ // window title. So, we need to remove the old window with the old title and add a
+ // new window with the new title.
+ removeView(removalReason = "New info has new window title: ${newInfo.windowTitle}")
+ }
+
+ // At this point, we're guaranteed to no longer be displaying a view.
+ // So, set up all our callbacks and inflate the view.
configurationController.addCallback(displayScaleListener)
// Wake the screen if necessary so the user will see the view. (Per b/239426653, we want
// the view to show over the dream state, so we should only wake up if the screen is
// completely off.)
if (!powerManager.isScreenOn) {
powerManager.wakeUp(
- SystemClock.uptimeMillis(),
- PowerManager.WAKE_REASON_APPLICATION,
- "com.android.systemui:$wakeReason",
+ SystemClock.uptimeMillis(),
+ PowerManager.WAKE_REASON_APPLICATION,
+ "com.android.systemui:${newInfo.wakeReason}",
)
}
- logger.logChipAddition()
+ logger.logViewAddition(newInfo.windowTitle)
inflateAndUpdateView(newInfo)
}
// Cancel and re-set the view timeout each time we get a new state.
val timeout = accessibilityManager.getRecommendedTimeoutMillis(
- newInfo.getTimeoutMs().toInt(),
+ newInfo.timeoutMs,
// Not all views have controls so FLAG_CONTENT_CONTROLS might be superfluous, but
// include it just to be safe.
FLAG_CONTENT_ICONS or FLAG_CONTENT_TEXT or FLAG_CONTENT_CONTROLS
@@ -147,7 +150,12 @@
val newDisplayInfo = DisplayInfo(newView, newInfo)
displayInfo = newDisplayInfo
updateView(newDisplayInfo.info, newDisplayInfo.view)
- windowManager.addView(newView, windowLayoutParams)
+
+ val paramsWithTitle = WindowManager.LayoutParams().also {
+ it.copyFrom(windowLayoutParams)
+ it.title = newInfo.windowTitle
+ }
+ windowManager.addView(newView, paramsWithTitle)
animateViewIn(newView)
}
@@ -177,7 +185,7 @@
val currentView = currentDisplayInfo.view
animateViewOut(currentView) { windowManager.removeView(currentView) }
- logger.logChipRemoval(removalReason)
+ logger.logViewRemoval(removalReason)
configurationController.removeCallback(displayScaleListener)
// Re-set to null immediately (instead as part of the animation end runnable) so
// that if a new view event comes in while this view is animating out, we still display the
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
index 4fe753a..cbb5002 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
@@ -19,12 +19,24 @@
/**
* A superclass view state used with [TemporaryViewDisplayController].
*/
-interface TemporaryViewInfo {
+abstract class TemporaryViewInfo {
/**
- * Returns the amount of time the given view state should display on the screen before it times
- * out and disappears.
+ * The title to use for the window that displays the temporary view. Should be normally cased,
+ * like "Window Title".
*/
- fun getTimeoutMs(): Long = DEFAULT_TIMEOUT_MILLIS
+ abstract val windowTitle: String
+
+ /**
+ * A string used for logging if we needed to wake the screen in order to display the temporary
+ * view. Should be screaming snake cased, like WAKE_REASON.
+ */
+ abstract val wakeReason: String
+
+ /**
+ * The amount of time the given view state should display on the screen before it times out and
+ * disappears.
+ */
+ open val timeoutMs: Int = DEFAULT_TIMEOUT_MILLIS
}
-const val DEFAULT_TIMEOUT_MILLIS = 10000L
+const val DEFAULT_TIMEOUT_MILLIS = 10000
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
index a7185cb..428a104 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
@@ -24,13 +24,13 @@
internal val buffer: LogBuffer,
internal val tag: String,
) {
- /** Logs that we added the chip to a new window. */
- fun logChipAddition() {
- buffer.log(tag, LogLevel.DEBUG, {}, { "Chip added" })
+ /** Logs that we added the view in a window titled [windowTitle]. */
+ fun logViewAddition(windowTitle: String) {
+ buffer.log(tag, LogLevel.DEBUG, { str1 = windowTitle }, { "View added. window=$str1" })
}
/** Logs that we removed the chip for the given [reason]. */
- fun logChipRemoval(reason: String) {
- buffer.log(tag, LogLevel.DEBUG, { str1 = reason }, { "Chip removed due to $str1" })
+ fun logViewRemoval(reason: String) {
+ buffer.log(tag, LogLevel.DEBUG, { str1 = reason }, { "View removed due to: $str1" })
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index b8930a4..87b6e8d 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -38,9 +38,6 @@
import com.android.systemui.common.ui.binder.TextViewBinder
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger
-import com.android.systemui.media.taptotransfer.common.MediaTttUtils
-import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -64,14 +61,11 @@
* Only one chipbar may be shown at a time.
* TODO(b/245610654): Should we just display whichever chipbar was most recently requested, or do we
* need to maintain a priority ordering?
- *
- * TODO(b/245610654): Remove all media-related items from this class so it's just for generic
- * chipbars.
*/
@SysUISingleton
open class ChipbarCoordinator @Inject constructor(
context: Context,
- @MediaTttSenderLogger logger: MediaTttLogger,
+ logger: ChipbarLogger,
windowManager: WindowManager,
@Main mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
@@ -81,7 +75,7 @@
private val falsingCollector: FalsingCollector,
private val viewUtil: ViewUtil,
private val vibratorHelper: VibratorHelper,
-) : TemporaryViewDisplayController<ChipbarInfo, MediaTttLogger>(
+) : TemporaryViewDisplayController<ChipbarInfo, ChipbarLogger>(
context,
logger,
windowManager,
@@ -90,8 +84,6 @@
configurationController,
powerManager,
R.layout.chipbar,
- MediaTttUtils.WINDOW_TITLE,
- MediaTttUtils.WAKE_REASON,
) {
private lateinit var parent: ChipbarRootView
@@ -106,7 +98,16 @@
newInfo: ChipbarInfo,
currentView: ViewGroup
) {
- // TODO(b/245610654): Adding logging here.
+ logger.logViewUpdate(
+ newInfo.windowTitle,
+ newInfo.text.loadText(context),
+ when (newInfo.endItem) {
+ null -> "null"
+ is ChipbarEndItem.Loading -> "loading"
+ is ChipbarEndItem.Error -> "error"
+ is ChipbarEndItem.Button -> "button(${newInfo.endItem.text.loadText(context)})"
+ }
+ )
// Detect falsing touches on the chip.
parent = currentView.requireViewById(R.id.chipbar_root_view)
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
index 57fde87..6237365 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
@@ -37,7 +37,10 @@
val text: Text,
val endItem: ChipbarEndItem?,
val vibrationEffect: VibrationEffect? = null,
-) : TemporaryViewInfo
+ override val windowTitle: String,
+ override val wakeReason: String,
+ override val timeoutMs: Int,
+) : TemporaryViewInfo()
/** The possible items to display at the end of the chipbar. */
sealed class ChipbarEndItem {
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
new file mode 100644
index 0000000..e477cd6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 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.temporarydisplay.chipbar
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.temporarydisplay.TemporaryViewLogger
+import com.android.systemui.temporarydisplay.dagger.ChipbarLog
+import javax.inject.Inject
+
+/** A logger for the chipbar. */
+@SysUISingleton
+class ChipbarLogger
+@Inject
+constructor(
+ @ChipbarLog buffer: LogBuffer,
+) : TemporaryViewLogger(buffer, "ChipbarLog") {
+ /**
+ * Logs that the chipbar was updated to display in a window named [windowTitle], with [text] and
+ * [endItemDesc].
+ */
+ fun logViewUpdate(windowTitle: String, text: String?, endItemDesc: String) {
+ buffer.log(
+ tag,
+ LogLevel.DEBUG,
+ {
+ str1 = windowTitle
+ str2 = text
+ str3 = endItemDesc
+ },
+ { "Chipbar updated. window=$str1 text=$str2 endItem=$str3" }
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/ChipbarLog.kt
similarity index 70%
copy from packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
copy to packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/ChipbarLog.kt
index 581dafa3..5f101f2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/ChipbarLog.kt
@@ -14,10 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.domain.model
+package com.android.systemui.temporarydisplay.dagger
-/** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */
-enum class KeyguardQuickAffordancePosition {
- BOTTOM_START,
- BOTTOM_END,
-}
+import javax.inject.Qualifier
+
+/** Status bar connectivity logs in table format. */
+@Qualifier
+@MustBeDocumented
+@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
+annotation class ChipbarLog
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
new file mode 100644
index 0000000..cf0a183
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 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.temporarydisplay.dagger
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.plugins.log.LogBuffer
+import dagger.Module
+import dagger.Provides
+
+@Module
+interface TemporaryDisplayModule {
+ @Module
+ companion object {
+ @JvmStatic
+ @Provides
+ @SysUISingleton
+ @ChipbarLog
+ fun provideChipbarLogBuffer(factory: LogBufferFactory): LogBuffer {
+ return factory.create("ChipbarLog", 40)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index b885d54..f9bec65 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -486,7 +486,7 @@
registeredSwipeListener.onSwipeUp();
- verify(mKeyguardUpdateMonitor).requestFaceAuth(true,
+ verify(mKeyguardUpdateMonitor).requestFaceAuth(
FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
}
@@ -499,16 +499,15 @@
registeredSwipeListener.onSwipeUp();
verify(mKeyguardUpdateMonitor, never())
- .requestFaceAuth(true,
- FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
+ .requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
}
@Test
public void onSwipeUp_whenFaceDetectionIsTriggered_hidesBouncerMessage() {
KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
getRegisteredSwipeListener();
- when(mKeyguardUpdateMonitor.requestFaceAuth(true,
- FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)).thenReturn(true);
+ when(mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER))
+ .thenReturn(true);
setupGetSecurityView();
registeredSwipeListener.onSwipeUp();
@@ -520,8 +519,8 @@
public void onSwipeUp_whenFaceDetectionIsNotTriggered_retainsBouncerMessage() {
KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
getRegisteredSwipeListener();
- when(mKeyguardUpdateMonitor.requestFaceAuth(true,
- FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)).thenReturn(false);
+ when(mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER))
+ .thenReturn(false);
setupGetSecurityView();
registeredSwipeListener.onSwipeUp();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 66be6ec..680c3b8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -676,7 +676,7 @@
bouncerFullyVisibleAndNotGoingToSleep();
mTestableLooper.processAllMessages();
- boolean didFaceAuthRun = mKeyguardUpdateMonitor.requestFaceAuth(true,
+ boolean didFaceAuthRun = mKeyguardUpdateMonitor.requestFaceAuth(
NOTIFICATION_PANEL_CLICKED);
assertThat(didFaceAuthRun).isTrue();
@@ -688,7 +688,7 @@
biometricsDisabledForCurrentUser();
mTestableLooper.processAllMessages();
- boolean didFaceAuthRun = mKeyguardUpdateMonitor.requestFaceAuth(true,
+ boolean didFaceAuthRun = mKeyguardUpdateMonitor.requestFaceAuth(
NOTIFICATION_PANEL_CLICKED);
assertThat(didFaceAuthRun).isFalse();
@@ -707,8 +707,7 @@
// Stop scanning when bouncer becomes visible
setKeyguardBouncerVisibility(true);
clearInvocations(mFaceManager);
- mKeyguardUpdateMonitor.requestFaceAuth(true,
- FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
+ mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
anyBoolean());
}
@@ -1695,7 +1694,7 @@
}
private void triggerSuccessfulFaceAuth() {
- mKeyguardUpdateMonitor.requestFaceAuth(true, FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
+ mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
verify(mFaceManager).authenticate(any(),
any(),
mAuthenticationCallbackCaptor.capture(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index bf6d574..78ee627 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -43,6 +43,7 @@
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;
@@ -54,6 +55,9 @@
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
+ @Mock
+ private DismissAnimationController.DismissCallback mStubDismissCallback;
+
private RecyclerView mStubListView;
private MenuView mMenuView;
private MenuItemAccessibilityDelegate mMenuItemAccessibilityDelegate;
@@ -87,7 +91,7 @@
mMenuItemAccessibilityDelegate.onInitializeAccessibilityNodeInfo(mStubListView, info);
- assertThat(info.getActionList().size()).isEqualTo(5);
+ assertThat(info.getActionList().size()).isEqualTo(6);
}
@Test
@@ -156,6 +160,17 @@
}
@Test
+ public void performRemoveMenuAction_success() {
+ mMenuAnimationController.setDismissCallback(mStubDismissCallback);
+ final boolean removeMenuAction =
+ mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
+ R.id.action_remove_menu, null);
+
+ assertThat(removeMenuAction).isTrue();
+ verify(mMenuAnimationController).removeMenu();
+ }
+
+ @Test
public void performFocusAction_fadeIn() {
mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
ACTION_ACCESSIBILITY_FOCUS, null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index d1107c6..45b8ce1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -41,10 +41,15 @@
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakePromptRepository
+import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor
+import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
import org.junit.After
import org.junit.Rule
import org.junit.Test
@@ -80,6 +85,15 @@
@Mock
lateinit var interactionJankMonitor: InteractionJankMonitor
+ private val biometricPromptRepository = FakePromptRepository()
+ private val credentialInteractor = FakeCredentialInteractor()
+ private val bpCredentialInteractor = BiometricPromptCredentialInteractor(
+ Dispatchers.Main.immediate,
+ biometricPromptRepository,
+ credentialInteractor
+ )
+ private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor)
+
private var authContainer: TestAuthContainerView? = null
@After
@@ -466,6 +480,8 @@
userManager,
lockPatternUtils,
interactionJankMonitor,
+ { bpCredentialInteractor },
+ { credentialViewModel },
Handler(TestableLooper.get(this).looper),
FakeExecutor(FakeSystemClock())
) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 88a806d..4dd46ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -89,6 +89,8 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
@@ -165,6 +167,11 @@
private UdfpsLogger mUdfpsLogger;
@Mock
private InteractionJankMonitor mInteractionJankMonitor;
+ @Mock
+ private BiometricPromptCredentialInteractor mBiometricPromptCredentialInteractor;
+ @Mock
+ private CredentialViewModel mCredentialViewModel;
+
@Captor
private ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mFpAuthenticatorsRegisteredCaptor;
@Captor
@@ -1009,6 +1016,7 @@
fingerprintManager, faceManager, udfpsControllerFactory,
sidefpsControllerFactory, mDisplayManager, mWakefulnessLifecycle,
mUserManager, mLockPatternUtils, mUdfpsLogger, statusBarStateController,
+ () -> mBiometricPromptCredentialInteractor, () -> mCredentialViewModel,
mInteractionJankMonitor, mHandler, mBackgroundExecutor, vibratorHelper);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
index 8820c16..1379a0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
@@ -22,12 +22,11 @@
import android.hardware.biometrics.ComponentInfoInternal
import android.hardware.biometrics.PromptInfo
import android.hardware.biometrics.SensorProperties
-import android.hardware.face.FaceSensorPropertiesInternal
import android.hardware.face.FaceSensorProperties
+import android.hardware.face.FaceSensorPropertiesInternal
import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.os.Bundle
-
import android.testing.ViewUtils
import android.view.LayoutInflater
@@ -83,26 +82,31 @@
internal fun fingerprintSensorPropertiesInternal(
ids: List<Int> = listOf(0)
): List<FingerprintSensorPropertiesInternal> {
- val componentInfo = listOf(
+ val componentInfo =
+ listOf(
ComponentInfoInternal(
- "fingerprintSensor" /* componentId */,
- "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
- "00000001" /* serialNumber */, "" /* softwareVersion */
+ "fingerprintSensor" /* componentId */,
+ "vendor/model/revision" /* hardwareVersion */,
+ "1.01" /* firmwareVersion */,
+ "00000001" /* serialNumber */,
+ "" /* softwareVersion */
),
ComponentInfoInternal(
- "matchingAlgorithm" /* componentId */,
- "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
- "vendor/version/revision" /* softwareVersion */
+ "matchingAlgorithm" /* componentId */,
+ "" /* hardwareVersion */,
+ "" /* firmwareVersion */,
+ "" /* serialNumber */,
+ "vendor/version/revision" /* softwareVersion */
)
- )
+ )
return ids.map { id ->
FingerprintSensorPropertiesInternal(
- id,
- SensorProperties.STRENGTH_STRONG,
- 5 /* maxEnrollmentsPerUser */,
- componentInfo,
- FingerprintSensorProperties.TYPE_REAR,
- false /* resetLockoutRequiresHardwareAuthToken */
+ id,
+ SensorProperties.STRENGTH_STRONG,
+ 5 /* maxEnrollmentsPerUser */,
+ componentInfo,
+ FingerprintSensorProperties.TYPE_REAR,
+ false /* resetLockoutRequiresHardwareAuthToken */
)
}
}
@@ -111,28 +115,53 @@
internal fun faceSensorPropertiesInternal(
ids: List<Int> = listOf(1)
): List<FaceSensorPropertiesInternal> {
- val componentInfo = listOf(
+ val componentInfo =
+ listOf(
ComponentInfoInternal(
- "faceSensor" /* componentId */,
- "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
- "00000001" /* serialNumber */, "" /* softwareVersion */
+ "faceSensor" /* componentId */,
+ "vendor/model/revision" /* hardwareVersion */,
+ "1.01" /* firmwareVersion */,
+ "00000001" /* serialNumber */,
+ "" /* softwareVersion */
),
ComponentInfoInternal(
- "matchingAlgorithm" /* componentId */,
- "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
- "vendor/version/revision" /* softwareVersion */
+ "matchingAlgorithm" /* componentId */,
+ "" /* hardwareVersion */,
+ "" /* firmwareVersion */,
+ "" /* serialNumber */,
+ "vendor/version/revision" /* softwareVersion */
)
- )
+ )
return ids.map { id ->
FaceSensorPropertiesInternal(
- id,
- SensorProperties.STRENGTH_STRONG,
- 2 /* maxEnrollmentsPerUser */,
- componentInfo,
- FaceSensorProperties.TYPE_RGB,
- true /* supportsFaceDetection */,
- true /* supportsSelfIllumination */,
- false /* resetLockoutRequiresHardwareAuthToken */
+ id,
+ SensorProperties.STRENGTH_STRONG,
+ 2 /* maxEnrollmentsPerUser */,
+ componentInfo,
+ FaceSensorProperties.TYPE_RGB,
+ true /* supportsFaceDetection */,
+ true /* supportsSelfIllumination */,
+ false /* resetLockoutRequiresHardwareAuthToken */
)
}
}
+
+internal fun promptInfo(
+ title: String = "title",
+ subtitle: String = "sub",
+ description: String = "desc",
+ credentialTitle: String? = "cred title",
+ credentialSubtitle: String? = "cred sub",
+ credentialDescription: String? = "cred desc",
+ negativeButton: String = "neg",
+): PromptInfo {
+ val info = PromptInfo()
+ info.title = title
+ info.subtitle = subtitle
+ info.description = description
+ credentialTitle?.let { info.deviceCredentialTitle = it }
+ credentialSubtitle?.let { info.deviceCredentialSubtitle = it }
+ credentialDescription?.let { info.deviceCredentialDescription = it }
+ info.negativeButtonText = negativeButton
+ return info
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 49c6fd1..ed957db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -69,6 +69,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -169,6 +170,8 @@
private FakeExecutor mFgExecutor;
@Mock
private UdfpsDisplayMode mUdfpsDisplayMode;
+ @Mock
+ private FeatureFlags mFeatureFlags;
// Stuff for configuring mocks
@Mock
@@ -250,6 +253,7 @@
mStatusBarKeyguardViewManager,
mDumpManager,
mKeyguardUpdateMonitor,
+ mFeatureFlags,
mFalsingManager,
mPowerManager,
mAccessibilityManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
new file mode 100644
index 0000000..2d5614c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
@@ -0,0 +1,81 @@
+package com.android.systemui.biometrics.data.repository
+
+import android.hardware.biometrics.PromptInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.data.model.PromptKind
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(JUnit4::class)
+class PromptRepositoryImplTest : SysuiTestCase() {
+
+ @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+ @Mock private lateinit var authController: AuthController
+
+ private lateinit var repository: PromptRepositoryImpl
+
+ @Before
+ fun setup() {
+ repository = PromptRepositoryImpl(authController)
+ }
+
+ @Test
+ fun isShowing() = runBlockingTest {
+ whenever(authController.isShowing).thenReturn(true)
+
+ val values = mutableListOf<Boolean>()
+ val job = launch { repository.isShowing.toList(values) }
+ assertThat(values).containsExactly(true)
+
+ withArgCaptor<AuthController.Callback> {
+ verify(authController).addCallback(capture())
+
+ value.onBiometricPromptShown()
+ assertThat(values).containsExactly(true, true)
+
+ value.onBiometricPromptDismissed()
+ assertThat(values).containsExactly(true, true, false).inOrder()
+
+ job.cancel()
+ verify(authController).removeCallback(eq(value))
+ }
+ }
+
+ @Test
+ fun setsAndUnsetsPrompt() = runBlockingTest {
+ val kind = PromptKind.PIN
+ val uid = 8
+ val challenge = 90L
+ val promptInfo = PromptInfo()
+
+ repository.setPrompt(promptInfo, uid, challenge, kind)
+
+ assertThat(repository.kind.value).isEqualTo(kind)
+ assertThat(repository.userId.value).isEqualTo(uid)
+ assertThat(repository.challenge.value).isEqualTo(challenge)
+ assertThat(repository.promptInfo.value).isSameInstanceAs(promptInfo)
+
+ repository.unsetPrompt()
+
+ assertThat(repository.promptInfo.value).isNull()
+ assertThat(repository.userId.value).isNull()
+ assertThat(repository.challenge.value).isNull()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
new file mode 100644
index 0000000..97d3e68
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
@@ -0,0 +1,216 @@
+package com.android.systemui.biometrics.domain.interactor
+
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyResourcesManager
+import android.content.pm.UserInfo
+import android.os.UserManager
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockscreenCredential
+import com.android.internal.widget.VerifyCredentialResponse
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
+import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
+import com.android.systemui.biometrics.domain.model.BiometricUserInfo
+import com.android.systemui.biometrics.promptInfo
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+private const val USER_ID = 22
+private const val OPERATION_ID = 100L
+private const val MAX_ATTEMPTS = 5
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class CredentialInteractorImplTest : SysuiTestCase() {
+
+ @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var userManager: UserManager
+ @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock private lateinit var devicePolicyResourcesManager: DevicePolicyResourcesManager
+
+ private val systemClock = FakeSystemClock()
+
+ private lateinit var interactor: CredentialInteractorImpl
+
+ @Before
+ fun setup() {
+ whenever(devicePolicyManager.resources).thenReturn(devicePolicyResourcesManager)
+ whenever(lockPatternUtils.getMaximumFailedPasswordsForWipe(anyInt()))
+ .thenReturn(MAX_ATTEMPTS)
+ whenever(userManager.getUserInfo(eq(USER_ID))).thenReturn(UserInfo(USER_ID, "", 0))
+ whenever(devicePolicyManager.getProfileWithMinimumFailedPasswordsForWipe(eq(USER_ID)))
+ .thenReturn(USER_ID)
+
+ interactor =
+ CredentialInteractorImpl(
+ mContext,
+ lockPatternUtils,
+ userManager,
+ devicePolicyManager,
+ systemClock
+ )
+ }
+
+ @Test
+ fun testStealthMode() {
+ for (value in listOf(true, false, false, true)) {
+ whenever(lockPatternUtils.isVisiblePatternEnabled(eq(USER_ID))).thenReturn(value)
+
+ assertThat(interactor.isStealthModeActive(USER_ID)).isEqualTo(!value)
+ }
+ }
+
+ @Test
+ fun testCredentialOwner() {
+ for (value in listOf(12, 8, 4)) {
+ whenever(userManager.getCredentialOwnerProfile(eq(USER_ID))).thenReturn(value)
+
+ assertThat(interactor.getCredentialOwnerOrSelfId(USER_ID)).isEqualTo(value)
+ }
+ }
+
+ @Test fun pinCredentialWhenGood() = pinCredential(goodCredential())
+
+ @Test fun pinCredentialWhenBad() = pinCredential(badCredential())
+
+ @Test fun pinCredentialWhenBadAndThrottled() = pinCredential(badCredential(timeout = 5_000))
+
+ private fun pinCredential(result: VerifyCredentialResponse) = runTest {
+ val usedAttempts = 1
+ whenever(lockPatternUtils.getCurrentFailedPasswordAttempts(eq(USER_ID)))
+ .thenReturn(usedAttempts)
+ whenever(lockPatternUtils.verifyCredential(any(), eq(USER_ID), anyInt())).thenReturn(result)
+ whenever(lockPatternUtils.verifyGatekeeperPasswordHandle(anyLong(), anyLong(), eq(USER_ID)))
+ .thenReturn(result)
+ whenever(lockPatternUtils.setLockoutAttemptDeadline(anyInt(), anyInt())).thenAnswer {
+ systemClock.elapsedRealtime() + (it.arguments[1] as Int)
+ }
+
+ // wrap in an async block so the test can advance the clock if throttling credential
+ // checks prevents the method from returning
+ val statusList = mutableListOf<CredentialStatus>()
+ interactor
+ .verifyCredential(pinRequest(), LockscreenCredential.createPin("1234"))
+ .toList(statusList)
+
+ val last = statusList.removeLastOrNull()
+ if (result.isMatched) {
+ assertThat(statusList).isEmpty()
+ val successfulResult = last as? CredentialStatus.Success.Verified
+ assertThat(successfulResult).isNotNull()
+ assertThat(successfulResult!!.hat).isEqualTo(result.gatekeeperHAT)
+
+ verify(lockPatternUtils).userPresent(eq(USER_ID))
+ verify(lockPatternUtils)
+ .removeGatekeeperPasswordHandle(eq(result.gatekeeperPasswordHandle))
+ } else {
+ val failedResult = last as? CredentialStatus.Fail.Error
+ assertThat(failedResult).isNotNull()
+ assertThat(failedResult!!.remainingAttempts)
+ .isEqualTo(if (result.timeout > 0) null else MAX_ATTEMPTS - usedAttempts - 1)
+ assertThat(failedResult.urgentMessage).isNull()
+
+ if (result.timeout > 0) { // failed and throttled
+ // messages are in the throttled errors, so the final Error.error is empty
+ assertThat(failedResult.error).isEmpty()
+ assertThat(statusList).isNotEmpty()
+ assertThat(statusList.filterIsInstance(CredentialStatus.Fail.Throttled::class.java))
+ .hasSize(statusList.size)
+
+ verify(lockPatternUtils).setLockoutAttemptDeadline(eq(USER_ID), eq(result.timeout))
+ } else { // failed
+ assertThat(failedResult.error)
+ .matches(Regex("(.*)try again(.*)", RegexOption.IGNORE_CASE).toPattern())
+ assertThat(statusList).isEmpty()
+
+ verify(lockPatternUtils).reportFailedPasswordAttempt(eq(USER_ID))
+ }
+ }
+ }
+
+ @Test
+ fun pinCredentialWhenBadAndFinalAttempt() = runTest {
+ whenever(lockPatternUtils.verifyCredential(any(), eq(USER_ID), anyInt()))
+ .thenReturn(badCredential())
+ whenever(lockPatternUtils.getCurrentFailedPasswordAttempts(eq(USER_ID)))
+ .thenReturn(MAX_ATTEMPTS - 2)
+
+ val statusList = mutableListOf<CredentialStatus>()
+ interactor
+ .verifyCredential(pinRequest(), LockscreenCredential.createPin("1234"))
+ .toList(statusList)
+
+ val result = statusList.removeLastOrNull() as? CredentialStatus.Fail.Error
+ assertThat(result).isNotNull()
+ assertThat(result!!.remainingAttempts).isEqualTo(1)
+ assertThat(result.urgentMessage).isNotEmpty()
+ assertThat(statusList).isEmpty()
+
+ verify(lockPatternUtils).reportFailedPasswordAttempt(eq(USER_ID))
+ }
+
+ @Test
+ fun pinCredentialWhenBadAndNoMoreAttempts() = runTest {
+ whenever(lockPatternUtils.verifyCredential(any(), eq(USER_ID), anyInt()))
+ .thenReturn(badCredential())
+ whenever(lockPatternUtils.getCurrentFailedPasswordAttempts(eq(USER_ID)))
+ .thenReturn(MAX_ATTEMPTS - 1)
+ whenever(devicePolicyResourcesManager.getString(any(), any())).thenReturn("wipe")
+
+ val statusList = mutableListOf<CredentialStatus>()
+ interactor
+ .verifyCredential(pinRequest(), LockscreenCredential.createPin("1234"))
+ .toList(statusList)
+
+ val result = statusList.removeLastOrNull() as? CredentialStatus.Fail.Error
+ assertThat(result).isNotNull()
+ assertThat(result!!.remainingAttempts).isEqualTo(0)
+ assertThat(result.urgentMessage).isNotEmpty()
+ assertThat(statusList).isEmpty()
+
+ verify(lockPatternUtils).reportFailedPasswordAttempt(eq(USER_ID))
+ }
+}
+
+private fun pinRequest(): BiometricPromptRequest.Credential.Pin =
+ BiometricPromptRequest.Credential.Pin(
+ promptInfo(),
+ BiometricUserInfo(USER_ID),
+ BiometricOperationInfo(OPERATION_ID)
+ )
+
+private fun goodCredential(
+ passwordHandle: Long = 90,
+ hat: ByteArray = ByteArray(69),
+): VerifyCredentialResponse =
+ VerifyCredentialResponse.Builder()
+ .setGatekeeperPasswordHandle(passwordHandle)
+ .setGatekeeperHAT(hat)
+ .build()
+
+private fun badCredential(timeout: Int = 0): VerifyCredentialResponse =
+ if (timeout > 0) {
+ VerifyCredentialResponse.fromTimeout(timeout)
+ } else {
+ VerifyCredentialResponse.fromError()
+ }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
new file mode 100644
index 0000000..dbcbf41
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
@@ -0,0 +1,270 @@
+package com.android.systemui.biometrics.domain.interactor
+
+import android.hardware.biometrics.PromptInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.Utils
+import com.android.systemui.biometrics.data.repository.FakePromptRepository
+import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
+import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
+import com.android.systemui.biometrics.domain.model.BiometricUserInfo
+import com.android.systemui.biometrics.promptInfo
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.junit.MockitoJUnit
+
+private const val USER_ID = 22
+private const val OPERATION_ID = 100L
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class PromptCredentialInteractorTest : SysuiTestCase() {
+
+ @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+ private val dispatcher = UnconfinedTestDispatcher()
+ private val biometricPromptRepository = FakePromptRepository()
+ private val credentialInteractor = FakeCredentialInteractor()
+
+ private lateinit var interactor: BiometricPromptCredentialInteractor
+
+ @Before
+ fun setup() {
+ interactor =
+ BiometricPromptCredentialInteractor(
+ dispatcher,
+ biometricPromptRepository,
+ credentialInteractor
+ )
+ }
+
+ @Test
+ fun testIsShowing() =
+ runTest(dispatcher) {
+ var showing = false
+ val job = launch { interactor.isShowing.collect { showing = it } }
+
+ biometricPromptRepository.setIsShowing(false)
+ assertThat(showing).isFalse()
+
+ biometricPromptRepository.setIsShowing(true)
+ assertThat(showing).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun testShowError() =
+ runTest(dispatcher) {
+ var error: CredentialStatus.Fail? = null
+ val job = launch { interactor.verificationError.collect { error = it } }
+
+ for (msg in listOf("once", "again")) {
+ interactor.setVerificationError(error(msg))
+ assertThat(error).isEqualTo(error(msg))
+ }
+
+ interactor.resetVerificationError()
+ assertThat(error).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun nullWhenNoPromptInfo() =
+ runTest(dispatcher) {
+ var prompt: BiometricPromptRequest? = null
+ val job = launch { interactor.prompt.collect { prompt = it } }
+
+ assertThat(prompt).isNull()
+
+ job.cancel()
+ }
+
+ @Test fun usePinCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PIN)
+
+ @Test fun usePasswordCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PASSWORD)
+
+ @Test fun usePatternCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PATTERN)
+
+ private fun useCredentialForPrompt(kind: Int) =
+ runTest(dispatcher) {
+ val isStealth = false
+ credentialInteractor.stealthMode = isStealth
+
+ var prompt: BiometricPromptRequest? = null
+ val job = launch { interactor.prompt.collect { prompt = it } }
+
+ val title = "what a prompt"
+ val subtitle = "s"
+ val description = "something to see"
+
+ interactor.useCredentialsForAuthentication(
+ PromptInfo().also {
+ it.title = title
+ it.description = description
+ it.subtitle = subtitle
+ },
+ kind = kind,
+ userId = USER_ID,
+ challenge = OPERATION_ID
+ )
+
+ val p = prompt as? BiometricPromptRequest.Credential
+ assertThat(p).isNotNull()
+ assertThat(p!!.title).isEqualTo(title)
+ assertThat(p.subtitle).isEqualTo(subtitle)
+ assertThat(p.description).isEqualTo(description)
+ assertThat(p.userInfo).isEqualTo(BiometricUserInfo(USER_ID))
+ assertThat(p.operationInfo).isEqualTo(BiometricOperationInfo(OPERATION_ID))
+ assertThat(p)
+ .isInstanceOf(
+ when (kind) {
+ Utils.CREDENTIAL_PIN -> BiometricPromptRequest.Credential.Pin::class.java
+ Utils.CREDENTIAL_PASSWORD ->
+ BiometricPromptRequest.Credential.Password::class.java
+ Utils.CREDENTIAL_PATTERN ->
+ BiometricPromptRequest.Credential.Pattern::class.java
+ else -> throw Exception("wrong kind")
+ }
+ )
+ if (p is BiometricPromptRequest.Credential.Pattern) {
+ assertThat(p.stealthMode).isEqualTo(isStealth)
+ }
+
+ interactor.resetPrompt()
+
+ assertThat(prompt).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun checkCredential() =
+ runTest(dispatcher) {
+ val hat = ByteArray(4)
+ credentialInteractor.verifyCredentialResponse = { _ -> flowOf(verified(hat)) }
+
+ val errors = mutableListOf<CredentialStatus.Fail?>()
+ val job = launch { interactor.verificationError.toList(errors) }
+
+ val checked =
+ interactor.checkCredential(pinRequest(), text = "1234")
+ as? CredentialStatus.Success.Verified
+
+ assertThat(checked).isNotNull()
+ assertThat(checked!!.hat).isSameInstanceAs(hat)
+ assertThat(errors.map { it?.error }).containsExactly(null)
+
+ job.cancel()
+ }
+
+ @Test
+ fun checkCredentialWhenBad() =
+ runTest(dispatcher) {
+ val errorMessage = "bad"
+ val remainingAttempts = 12
+ credentialInteractor.verifyCredentialResponse = { _ ->
+ flowOf(error(errorMessage, remainingAttempts))
+ }
+
+ val errors = mutableListOf<CredentialStatus.Fail?>()
+ val job = launch { interactor.verificationError.toList(errors) }
+
+ val checked =
+ interactor.checkCredential(pinRequest(), text = "1234")
+ as? CredentialStatus.Fail.Error
+
+ assertThat(checked).isNotNull()
+ assertThat(checked!!.remainingAttempts).isEqualTo(remainingAttempts)
+ assertThat(checked.urgentMessage).isNull()
+ assertThat(errors.map { it?.error }).containsExactly(null, errorMessage).inOrder()
+
+ job.cancel()
+ }
+
+ @Test
+ fun checkCredentialWhenBadAndUrgentMessage() =
+ runTest(dispatcher) {
+ val error = "not so bad"
+ val urgentMessage = "really bad"
+ credentialInteractor.verifyCredentialResponse = { _ ->
+ flowOf(error(error, 10, urgentMessage))
+ }
+
+ val errors = mutableListOf<CredentialStatus.Fail?>()
+ val job = launch { interactor.verificationError.toList(errors) }
+
+ val checked =
+ interactor.checkCredential(pinRequest(), text = "1234")
+ as? CredentialStatus.Fail.Error
+
+ assertThat(checked).isNotNull()
+ assertThat(checked!!.urgentMessage).isEqualTo(urgentMessage)
+ assertThat(errors.map { it?.error }).containsExactly(null, error).inOrder()
+ assertThat(errors.last() as? CredentialStatus.Fail.Error)
+ .isEqualTo(error(error, 10, urgentMessage))
+
+ job.cancel()
+ }
+
+ @Test
+ fun checkCredentialWhenBadAndThrottled() =
+ runTest(dispatcher) {
+ val remainingAttempts = 3
+ val error = ":("
+ val urgentMessage = ":D"
+ credentialInteractor.verifyCredentialResponse = { _ ->
+ flow {
+ for (i in 1..3) {
+ emit(throttled("$i"))
+ delay(100)
+ }
+ emit(error(error, remainingAttempts, urgentMessage))
+ }
+ }
+ val errors = mutableListOf<CredentialStatus.Fail?>()
+ val job = launch { interactor.verificationError.toList(errors) }
+
+ val checked =
+ interactor.checkCredential(pinRequest(), text = "1234")
+ as? CredentialStatus.Fail.Error
+
+ assertThat(checked).isNotNull()
+ assertThat(checked!!.remainingAttempts).isEqualTo(remainingAttempts)
+ assertThat(checked.urgentMessage).isEqualTo(urgentMessage)
+ assertThat(errors.map { it?.error })
+ .containsExactly(null, "1", "2", "3", error)
+ .inOrder()
+
+ job.cancel()
+ }
+}
+
+private fun pinRequest(): BiometricPromptRequest.Credential.Pin =
+ BiometricPromptRequest.Credential.Pin(
+ promptInfo(),
+ BiometricUserInfo(USER_ID),
+ BiometricOperationInfo(OPERATION_ID)
+ )
+
+private fun verified(hat: ByteArray) = CredentialStatus.Success.Verified(hat)
+
+private fun throttled(error: String) = CredentialStatus.Fail.Throttled(error)
+
+private fun error(error: String? = null, remaining: Int? = null, urgentMessage: String? = null) =
+ CredentialStatus.Fail.Error(error, remaining, urgentMessage)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
new file mode 100644
index 0000000..4c5e3c1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
@@ -0,0 +1,93 @@
+package com.android.systemui.biometrics.domain.model
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.promptInfo
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+private const val USER_ID = 2
+private const val OPERATION_ID = 8L
+
+@SmallTest
+@RunWith(JUnit4::class)
+class BiometricPromptRequestTest : SysuiTestCase() {
+
+ @Test
+ fun biometricRequestFromPromptInfo() {
+ val title = "what"
+ val subtitle = "a"
+ val description = "request"
+
+ val request =
+ BiometricPromptRequest.Biometric(
+ promptInfo(title = title, subtitle = subtitle, description = description),
+ BiometricUserInfo(USER_ID),
+ BiometricOperationInfo(OPERATION_ID)
+ )
+
+ assertThat(request.title).isEqualTo(title)
+ assertThat(request.subtitle).isEqualTo(subtitle)
+ assertThat(request.description).isEqualTo(description)
+ assertThat(request.userInfo).isEqualTo(BiometricUserInfo(USER_ID))
+ assertThat(request.operationInfo).isEqualTo(BiometricOperationInfo(OPERATION_ID))
+ }
+
+ @Test
+ fun credentialRequestFromPromptInfo() {
+ val title = "what"
+ val subtitle = "a"
+ val description = "request"
+ val stealth = true
+
+ val toCheck =
+ listOf(
+ BiometricPromptRequest.Credential.Pin(
+ promptInfo(
+ title = title,
+ subtitle = subtitle,
+ description = description,
+ credentialTitle = null,
+ credentialSubtitle = null,
+ credentialDescription = null
+ ),
+ BiometricUserInfo(USER_ID),
+ BiometricOperationInfo(OPERATION_ID)
+ ),
+ BiometricPromptRequest.Credential.Password(
+ promptInfo(
+ credentialTitle = title,
+ credentialSubtitle = subtitle,
+ credentialDescription = description
+ ),
+ BiometricUserInfo(USER_ID),
+ BiometricOperationInfo(OPERATION_ID)
+ ),
+ BiometricPromptRequest.Credential.Pattern(
+ promptInfo(
+ subtitle = subtitle,
+ description = description,
+ credentialTitle = title,
+ credentialSubtitle = null,
+ credentialDescription = null
+ ),
+ BiometricUserInfo(USER_ID),
+ BiometricOperationInfo(OPERATION_ID),
+ stealth
+ )
+ )
+
+ for (request in toCheck) {
+ assertThat(request.title).isEqualTo(title)
+ assertThat(request.subtitle).isEqualTo(subtitle)
+ assertThat(request.description).isEqualTo(description)
+ assertThat(request.userInfo).isEqualTo(BiometricUserInfo(USER_ID))
+ assertThat(request.operationInfo).isEqualTo(BiometricOperationInfo(OPERATION_ID))
+ if (request is BiometricPromptRequest.Credential.Pattern) {
+ assertThat(request.stealthMode).isEqualTo(stealth)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt
new file mode 100644
index 0000000..d73cdfc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt
@@ -0,0 +1,181 @@
+package com.android.systemui.biometrics.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.model.PromptKind
+import com.android.systemui.biometrics.data.repository.FakePromptRepository
+import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor
+import com.android.systemui.biometrics.domain.interactor.CredentialStatus
+import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor
+import com.android.systemui.biometrics.promptInfo
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+private const val USER_ID = 9
+private const val OPERATION_ID = 10L
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class CredentialViewModelTest : SysuiTestCase() {
+
+ private val dispatcher = UnconfinedTestDispatcher()
+ private val promptRepository = FakePromptRepository()
+ private val credentialInteractor = FakeCredentialInteractor()
+
+ private lateinit var viewModel: CredentialViewModel
+
+ @Before
+ fun setup() {
+ viewModel =
+ CredentialViewModel(
+ mContext,
+ BiometricPromptCredentialInteractor(
+ dispatcher,
+ promptRepository,
+ credentialInteractor
+ )
+ )
+ }
+
+ @Test fun setsPinInputFlags() = setsInputFlags(PromptKind.PIN, expectFlags = true)
+ @Test fun setsPasswordInputFlags() = setsInputFlags(PromptKind.PASSWORD, expectFlags = false)
+ @Test fun setsPatternInputFlags() = setsInputFlags(PromptKind.PATTERN, expectFlags = false)
+
+ private fun setsInputFlags(type: PromptKind, expectFlags: Boolean) =
+ runTestWithKind(type) {
+ var flags: Int? = null
+ val job = launch { viewModel.inputFlags.collect { flags = it } }
+
+ if (expectFlags) {
+ assertThat(flags).isNotNull()
+ } else {
+ assertThat(flags).isNull()
+ }
+ job.cancel()
+ }
+
+ @Test fun isStealthIgnoredByPin() = isStealthMode(PromptKind.PIN, expectStealth = false)
+ @Test
+ fun isStealthIgnoredByPassword() = isStealthMode(PromptKind.PASSWORD, expectStealth = false)
+ @Test fun isStealthUsedByPattern() = isStealthMode(PromptKind.PATTERN, expectStealth = true)
+
+ private fun isStealthMode(type: PromptKind, expectStealth: Boolean) =
+ runTestWithKind(type, init = { credentialInteractor.stealthMode = true }) {
+ var stealth: Boolean? = null
+ val job = launch { viewModel.stealthMode.collect { stealth = it } }
+
+ assertThat(stealth).isEqualTo(expectStealth)
+
+ job.cancel()
+ }
+
+ @Test
+ fun animatesContents() = runTestWithKind {
+ val expected = arrayOf(true, false, true)
+ val animate = mutableListOf<Boolean>()
+ val job = launch { viewModel.animateContents.toList(animate) }
+
+ for (value in expected) {
+ viewModel.setAnimateContents(value)
+ viewModel.setAnimateContents(value)
+ }
+ assertThat(animate).containsExactly(*expected).inOrder()
+
+ job.cancel()
+ }
+
+ @Test
+ fun showAndClearErrors() = runTestWithKind {
+ var error = ""
+ val job = launch { viewModel.errorMessage.collect { error = it } }
+ assertThat(error).isEmpty()
+
+ viewModel.showPatternTooShortError()
+ assertThat(error).isNotEmpty()
+
+ viewModel.resetErrorMessage()
+ assertThat(error).isEmpty()
+
+ job.cancel()
+ }
+
+ @Test
+ fun checkCredential() = runTestWithKind {
+ val hat = ByteArray(2)
+ credentialInteractor.verifyCredentialResponse = { _ ->
+ flowOf(CredentialStatus.Success.Verified(hat))
+ }
+
+ val attestations = mutableListOf<ByteArray?>()
+ val remainingAttempts = mutableListOf<RemainingAttempts?>()
+ var header: HeaderViewModel? = null
+ val job = launch {
+ launch { viewModel.validatedAttestation.toList(attestations) }
+ launch { viewModel.remainingAttempts.toList(remainingAttempts) }
+ launch { viewModel.header.collect { header = it } }
+ }
+ assertThat(header).isNotNull()
+
+ viewModel.checkCredential("p", header!!)
+
+ val attestation = attestations.removeLastOrNull()
+ assertThat(attestation).isSameInstanceAs(hat)
+ assertThat(attestations).isEmpty()
+ assertThat(remainingAttempts).containsExactly(RemainingAttempts())
+
+ job.cancel()
+ }
+
+ @Test
+ fun checkCredentialWhenBad() = runTestWithKind {
+ val remaining = 2
+ val urgentError = "wow"
+ credentialInteractor.verifyCredentialResponse = { _ ->
+ flowOf(CredentialStatus.Fail.Error("error", remaining, urgentError))
+ }
+
+ val attestations = mutableListOf<ByteArray?>()
+ val remainingAttempts = mutableListOf<RemainingAttempts?>()
+ var header: HeaderViewModel? = null
+ val job = launch {
+ launch { viewModel.validatedAttestation.toList(attestations) }
+ launch { viewModel.remainingAttempts.toList(remainingAttempts) }
+ launch { viewModel.header.collect { header = it } }
+ }
+ assertThat(header).isNotNull()
+
+ viewModel.checkCredential("1111", header!!)
+
+ assertThat(attestations).containsExactly(null)
+
+ val attemptInfo = remainingAttempts.removeLastOrNull()
+ assertThat(attemptInfo).isNotNull()
+ assertThat(attemptInfo!!.remaining).isEqualTo(remaining)
+ assertThat(attemptInfo.message).isEqualTo(urgentError)
+ assertThat(remainingAttempts).containsExactly(RemainingAttempts()) // initial value
+
+ job.cancel()
+ }
+
+ private fun runTestWithKind(
+ kind: PromptKind = PromptKind.PIN,
+ init: () -> Unit = {},
+ block: suspend TestScope.() -> Unit,
+ ) =
+ runTest(dispatcher) {
+ init()
+ promptRepository.setPrompt(promptInfo(), USER_ID, OPERATION_ID, kind)
+ block()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
index 20a82c6..4b3b70e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
@@ -88,6 +88,7 @@
flagMap,
restarter
)
+ mFeatureFlagsDebug.init()
verify(flagManager).onSettingsChangedAction = any()
broadcastReceiver = withArgCaptor {
verify(mockContext).registerReceiver(capture(), any(), nullable(), nullable(),
@@ -255,11 +256,11 @@
broadcastReceiver.onReceive(mockContext, Intent())
broadcastReceiver.onReceive(mockContext, Intent("invalid action"))
broadcastReceiver.onReceive(mockContext, Intent(FlagManager.ACTION_SET_FLAG))
- setByBroadcast(0, false) // unknown id does nothing
- setByBroadcast(1, "string") // wrong type does nothing
- setByBroadcast(2, 123) // wrong type does nothing
- setByBroadcast(3, false) // wrong type does nothing
- setByBroadcast(4, 123) // wrong type does nothing
+ setByBroadcast(0, false) // unknown id does nothing
+ setByBroadcast(1, "string") // wrong type does nothing
+ setByBroadcast(2, 123) // wrong type does nothing
+ setByBroadcast(3, false) // wrong type does nothing
+ setByBroadcast(4, 123) // wrong type does nothing
verifyNoMoreInteractions(flagManager, secureSettings)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
index 575c142..b2dd60c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
@@ -38,8 +38,9 @@
@Mock private lateinit var mResources: Resources
@Mock private lateinit var mSystemProperties: SystemPropertiesHelper
+ @Mock private lateinit var restarter: Restarter
+ private val flagMap = mutableMapOf<Int, Flag<*>>()
private val serverFlagReader = ServerFlagReaderFake()
-
private val deviceConfig = DeviceConfigProxyFake()
@Before
@@ -49,7 +50,9 @@
mResources,
mSystemProperties,
deviceConfig,
- serverFlagReader)
+ serverFlagReader,
+ flagMap,
+ restarter)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
new file mode 100644
index 0000000..6f5f460
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 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.flags
+
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.DeviceConfigProxyFake
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ServerFlagReaderImplTest : SysuiTestCase() {
+
+ private val NAMESPACE = "test"
+
+ @Mock private lateinit var changeListener: ServerFlagReader.ChangeListener
+
+ private lateinit var serverFlagReader: ServerFlagReaderImpl
+ private val deviceConfig = DeviceConfigProxyFake()
+ private val executor = FakeExecutor(FakeSystemClock())
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ serverFlagReader = ServerFlagReaderImpl(NAMESPACE, deviceConfig, executor)
+ }
+
+ @Test
+ fun testChange_alertsListener() {
+ val flag = ReleasedFlag(1)
+ serverFlagReader.listenForChanges(listOf(flag), changeListener)
+
+ deviceConfig.setProperty(NAMESPACE, "flag_override_1", "1", false)
+ executor.runAllReady()
+
+ verify(changeListener).onChange()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
similarity index 62%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
index e99c139..f18acba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
@@ -15,10 +15,10 @@
*
*/
-package com.android.systemui.keyguard.domain.quickaffordance
+package com.android.systemui.keyguard.data.quickaffordance
import com.android.systemui.animation.Expandable
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.yield
@@ -29,24 +29,27 @@
* This class is abstract to force tests to provide extensions of it as the system that references
* these configs uses each implementation's class type to refer to them.
*/
-abstract class FakeKeyguardQuickAffordanceConfig : KeyguardQuickAffordanceConfig {
+abstract class FakeKeyguardQuickAffordanceConfig(
+ override val key: String,
+) : KeyguardQuickAffordanceConfig {
- var onClickedResult: OnClickedResult = OnClickedResult.Handled
+ var onTriggeredResult: OnTriggeredResult = OnTriggeredResult.Handled
- private val _state =
- MutableStateFlow<KeyguardQuickAffordanceConfig.State>(
- KeyguardQuickAffordanceConfig.State.Hidden
+ private val _lockScreenState =
+ MutableStateFlow<KeyguardQuickAffordanceConfig.LockScreenState>(
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden
)
- override val state: Flow<KeyguardQuickAffordanceConfig.State> = _state
+ override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
+ _lockScreenState
- override fun onQuickAffordanceClicked(
+ override fun onTriggered(
expandable: Expandable?,
- ): OnClickedResult {
- return onClickedResult
+ ): OnTriggeredResult {
+ return onTriggeredResult
}
- suspend fun setState(state: KeyguardQuickAffordanceConfig.State) {
- _state.value = state
+ suspend fun setState(lockScreenState: KeyguardQuickAffordanceConfig.LockScreenState) {
+ _lockScreenState.value = lockScreenState
// Yield to allow the test's collection coroutine to "catch up" and collect this value
// before the test continues to the next line.
// TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
similarity index 83%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
index 9a91ea91..c94cec6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
@@ -1,21 +1,21 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2022 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
+ * 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
+ * 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.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*
*/
-package com.android.systemui.keyguard.domain.quickaffordance
+package com.android.systemui.keyguard.data.quickaffordance
import androidx.test.filters.SmallTest
import com.android.systemui.R
@@ -122,8 +122,8 @@
emptyList()
}
)
- val values = mutableListOf<KeyguardQuickAffordanceConfig.State>()
- val job = underTest.state.onEach(values::add).launchIn(this)
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+ val job = underTest.lockScreenState.onEach(values::add).launchIn(this)
if (canShowWhileLocked) {
verify(controlsListingController).addCallback(callbackCaptor.capture())
@@ -139,9 +139,9 @@
assertThat(values.last())
.isInstanceOf(
if (isVisibleExpected) {
- KeyguardQuickAffordanceConfig.State.Visible::class.java
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible::class.java
} else {
- KeyguardQuickAffordanceConfig.State.Hidden::class.java
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden::class.java
}
)
job.cancel()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
similarity index 65%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
index a809f05..659c1e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
@@ -1,21 +1,21 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2022 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
+ * 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
+ * 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.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*
*/
-package com.android.systemui.keyguard.domain.quickaffordance
+package com.android.systemui.keyguard.data.quickaffordance
import androidx.test.filters.SmallTest
import com.android.systemui.R
@@ -23,7 +23,7 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.dagger.ControlsComponent
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import java.util.Optional
@@ -72,11 +72,11 @@
whenever(component.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
whenever(controlsController.getFavorites()).thenReturn(listOf(mock()))
- val values = mutableListOf<KeyguardQuickAffordanceConfig.State>()
- val job = underTest.state.onEach(values::add).launchIn(this)
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+ val job = underTest.lockScreenState.onEach(values::add).launchIn(this)
assertThat(values.last())
- .isInstanceOf(KeyguardQuickAffordanceConfig.State.Hidden::class.java)
+ .isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Hidden::class.java)
job.cancel()
}
@@ -91,31 +91,32 @@
whenever(component.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
whenever(controlsController.getFavorites()).thenReturn(listOf(mock()))
- val values = mutableListOf<KeyguardQuickAffordanceConfig.State>()
- val job = underTest.state.onEach(values::add).launchIn(this)
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+ val job = underTest.lockScreenState.onEach(values::add).launchIn(this)
assertThat(values.last())
- .isInstanceOf(KeyguardQuickAffordanceConfig.State.Hidden::class.java)
+ .isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Hidden::class.java)
job.cancel()
}
@Test
- fun `onQuickAffordanceClicked - canShowWhileLockedSetting is true`() = runBlockingTest {
+ fun `onQuickAffordanceTriggered - canShowWhileLockedSetting is true`() = runBlockingTest {
whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(true))
- val onClickedResult = underTest.onQuickAffordanceClicked(expandable)
+ val onClickedResult = underTest.onTriggered(expandable)
- assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java)
- assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isTrue()
+ assertThat(onClickedResult).isInstanceOf(OnTriggeredResult.StartActivity::class.java)
+ assertThat((onClickedResult as OnTriggeredResult.StartActivity).canShowWhileLocked).isTrue()
}
@Test
- fun `onQuickAffordanceClicked - canShowWhileLockedSetting is false`() = runBlockingTest {
+ fun `onQuickAffordanceTriggered - canShowWhileLockedSetting is false`() = runBlockingTest {
whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(false))
- val onClickedResult = underTest.onQuickAffordanceClicked(expandable)
+ val onClickedResult = underTest.onTriggered(expandable)
- assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java)
- assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isFalse()
+ assertThat(onClickedResult).isInstanceOf(OnTriggeredResult.StartActivity::class.java)
+ assertThat((onClickedResult as OnTriggeredResult.StartActivity).canShowWhileLocked)
+ .isFalse()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
similarity index 70%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index 329c4db..61a3f9f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -1,26 +1,26 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2022 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
+ * 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
+ * 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.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*
*/
-package com.android.systemui.keyguard.domain.quickaffordance
+package com.android.systemui.keyguard.data.quickaffordance
import android.content.Intent
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
@@ -56,9 +56,9 @@
@Test
fun `affordance - sets up registration and delivers initial model`() = runBlockingTest {
whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
- var latest: KeyguardQuickAffordanceConfig.State? = null
+ var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
- val job = underTest.state.onEach { latest = it }.launchIn(this)
+ val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
verify(controller).addCallback(callbackCaptor.capture())
@@ -77,8 +77,8 @@
fun `affordance - scanner activity changed - delivers model with updated intent`() =
runBlockingTest {
whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
- var latest: KeyguardQuickAffordanceConfig.State? = null
- val job = underTest.state.onEach { latest = it }.launchIn(this)
+ var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
+ val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
verify(controller).addCallback(callbackCaptor.capture())
@@ -93,8 +93,8 @@
@Test
fun `affordance - scanner preference changed - delivers visible model`() = runBlockingTest {
- var latest: KeyguardQuickAffordanceConfig.State? = null
- val job = underTest.state.onEach { latest = it }.launchIn(this)
+ var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
+ val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
verify(controller).addCallback(callbackCaptor.capture())
@@ -109,34 +109,35 @@
@Test
fun `affordance - scanner preference changed - delivers none`() = runBlockingTest {
- var latest: KeyguardQuickAffordanceConfig.State? = null
- val job = underTest.state.onEach { latest = it }.launchIn(this)
+ var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
+ val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
verify(controller).addCallback(callbackCaptor.capture())
whenever(controller.isEnabledForLockScreenButton).thenReturn(false)
callbackCaptor.value.onQRCodeScannerPreferenceChanged()
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
job.cancel()
verify(controller).removeCallback(callbackCaptor.value)
}
@Test
- fun onQuickAffordanceClicked() {
- assertThat(underTest.onQuickAffordanceClicked(mock()))
+ fun onQuickAffordanceTriggered() {
+ assertThat(underTest.onTriggered(mock()))
.isEqualTo(
- OnClickedResult.StartActivity(
+ OnTriggeredResult.StartActivity(
intent = INTENT_1,
canShowWhileLocked = true,
)
)
}
- private fun assertVisibleState(latest: KeyguardQuickAffordanceConfig.State?) {
- assertThat(latest).isInstanceOf(KeyguardQuickAffordanceConfig.State.Visible::class.java)
- val visibleState = latest as KeyguardQuickAffordanceConfig.State.Visible
+ private fun assertVisibleState(latest: KeyguardQuickAffordanceConfig.LockScreenState?) {
+ assertThat(latest)
+ .isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible::class.java)
+ val visibleState = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible
assertThat(visibleState.icon).isNotNull()
assertThat(visibleState.icon.contentDescription).isNotNull()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
similarity index 74%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index 98dc4c4..c05beef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -1,21 +1,21 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2022 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
+ * 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
+ * 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.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*
*/
-package com.android.systemui.keyguard.domain.quickaffordance
+package com.android.systemui.keyguard.data.quickaffordance
import android.graphics.drawable.Drawable
import android.service.quickaccesswallet.GetWalletCardsResponse
@@ -67,11 +67,11 @@
@Test
fun `affordance - keyguard showing - has wallet card - visible model`() = runBlockingTest {
setUpState()
- var latest: KeyguardQuickAffordanceConfig.State? = null
+ var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
- val job = underTest.state.onEach { latest = it }.launchIn(this)
+ val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
- val visibleModel = latest as KeyguardQuickAffordanceConfig.State.Visible
+ val visibleModel = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible
assertThat(visibleModel.icon)
.isEqualTo(
Icon.Loaded(
@@ -88,11 +88,11 @@
@Test
fun `affordance - wallet not enabled - model is none`() = runBlockingTest {
setUpState(isWalletEnabled = false)
- var latest: KeyguardQuickAffordanceConfig.State? = null
+ var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
- val job = underTest.state.onEach { latest = it }.launchIn(this)
+ val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
job.cancel()
}
@@ -100,11 +100,11 @@
@Test
fun `affordance - query not successful - model is none`() = runBlockingTest {
setUpState(isWalletQuerySuccessful = false)
- var latest: KeyguardQuickAffordanceConfig.State? = null
+ var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
- val job = underTest.state.onEach { latest = it }.launchIn(this)
+ val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
job.cancel()
}
@@ -112,11 +112,11 @@
@Test
fun `affordance - missing icon - model is none`() = runBlockingTest {
setUpState(hasWalletIcon = false)
- var latest: KeyguardQuickAffordanceConfig.State? = null
+ var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
- val job = underTest.state.onEach { latest = it }.launchIn(this)
+ val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
job.cancel()
}
@@ -124,24 +124,24 @@
@Test
fun `affordance - no selected card - model is none`() = runBlockingTest {
setUpState(hasWalletIcon = false)
- var latest: KeyguardQuickAffordanceConfig.State? = null
+ var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
- val job = underTest.state.onEach { latest = it }.launchIn(this)
+ val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
job.cancel()
}
@Test
- fun onQuickAffordanceClicked() {
+ fun onQuickAffordanceTriggered() {
val animationController: ActivityLaunchAnimator.Controller = mock()
val expandable: Expandable = mock {
whenever(this.activityLaunchController()).thenReturn(animationController)
}
- assertThat(underTest.onQuickAffordanceClicked(expandable))
- .isEqualTo(KeyguardQuickAffordanceConfig.OnClickedResult.Handled)
+ assertThat(underTest.onTriggered(expandable))
+ .isEqualTo(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled)
verify(walletController)
.startQuickAccessUiIntent(
activityStarter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index b4d5464..7116cc1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -25,11 +25,12 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -211,7 +212,11 @@
MockitoAnnotations.initMocks(this)
whenever(expandable.activityLaunchController()).thenReturn(animationController)
- homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
+ homeControls =
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
+ ) {}
underTest =
KeyguardQuickAffordanceInteractor(
keyguardInteractor = KeyguardInteractor(repository = FakeKeyguardRepository()),
@@ -224,8 +229,14 @@
),
KeyguardQuickAffordancePosition.BOTTOM_END to
listOf(
- object : FakeKeyguardQuickAffordanceConfig() {},
- object : FakeKeyguardQuickAffordanceConfig() {},
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ ) {},
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
+ ) {},
),
),
),
@@ -237,30 +248,30 @@
}
@Test
- fun onQuickAffordanceClicked() = runBlockingTest {
+ fun onQuickAffordanceTriggered() = runBlockingTest {
setUpMocks(
needStrongAuthAfterBoot = needStrongAuthAfterBoot,
keyguardIsUnlocked = keyguardIsUnlocked,
)
homeControls.setState(
- state =
- KeyguardQuickAffordanceConfig.State.Visible(
+ lockScreenState =
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon = DRAWABLE,
)
)
- homeControls.onClickedResult =
+ homeControls.onTriggeredResult =
if (startActivity) {
- KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
intent = INTENT,
canShowWhileLocked = canShowWhileLocked,
)
} else {
- KeyguardQuickAffordanceConfig.OnClickedResult.Handled
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
}
- underTest.onQuickAffordanceClicked(
- configKey = homeControls::class,
+ underTest.onQuickAffordanceTriggered(
+ configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
expandable = expandable,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 65fd6e5..ae32ba6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -22,13 +22,14 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -69,9 +70,21 @@
repository = FakeKeyguardRepository()
repository.setKeyguardShowing(true)
- homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
- quickAccessWallet = object : FakeKeyguardQuickAffordanceConfig() {}
- qrCodeScanner = object : FakeKeyguardQuickAffordanceConfig() {}
+ homeControls =
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
+ ) {}
+ quickAccessWallet =
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ ) {}
+ qrCodeScanner =
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
+ ) {}
underTest =
KeyguardQuickAffordanceInteractor(
@@ -99,11 +112,11 @@
@Test
fun `quickAffordance - bottom start affordance is visible`() = runBlockingTest {
- val configKey = homeControls::class
+ val configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
homeControls.setState(
- KeyguardQuickAffordanceConfig.State.Visible(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon = ICON,
- toggle = KeyguardQuickAffordanceToggleState.On,
+ activationState = ActivationState.Active,
)
)
@@ -124,15 +137,15 @@
assertThat(visibleModel.icon).isEqualTo(ICON)
assertThat(visibleModel.icon.contentDescription)
.isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
- assertThat(visibleModel.toggle).isEqualTo(KeyguardQuickAffordanceToggleState.On)
+ assertThat(visibleModel.activationState).isEqualTo(ActivationState.Active)
job.cancel()
}
@Test
fun `quickAffordance - bottom end affordance is visible`() = runBlockingTest {
- val configKey = quickAccessWallet::class
+ val configKey = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
quickAccessWallet.setState(
- KeyguardQuickAffordanceConfig.State.Visible(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon = ICON,
)
)
@@ -154,7 +167,7 @@
assertThat(visibleModel.icon).isEqualTo(ICON)
assertThat(visibleModel.icon.contentDescription)
.isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
- assertThat(visibleModel.toggle).isEqualTo(KeyguardQuickAffordanceToggleState.NotSupported)
+ assertThat(visibleModel.activationState).isEqualTo(ActivationState.NotSupported)
job.cancel()
}
@@ -162,7 +175,7 @@
fun `quickAffordance - bottom start affordance hidden while dozing`() = runBlockingTest {
repository.setDozing(true)
homeControls.setState(
- KeyguardQuickAffordanceConfig.State.Visible(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon = ICON,
)
)
@@ -182,7 +195,7 @@
runBlockingTest {
repository.setKeyguardShowing(false)
homeControls.setState(
- KeyguardQuickAffordanceConfig.State.Visible(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon = ICON,
)
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
index e68c43f..13e2768 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
@@ -17,8 +17,8 @@
package com.android.systemui.keyguard.domain.quickaffordance
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import kotlin.reflect.KClass
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
/** Fake implementation of [FakeKeyguardQuickAffordanceRegistry], for tests. */
class FakeKeyguardQuickAffordanceRegistry(
@@ -33,11 +33,8 @@
}
override fun get(
- configClass: KClass<out FakeKeyguardQuickAffordanceConfig>
+ key: String,
): FakeKeyguardQuickAffordanceConfig {
- return configsByPosition.values
- .flatten()
- .associateBy { config -> config::class }
- .getValue(configClass)
+ return configsByPosition.values.flatten().associateBy { config -> config.key }.getValue(key)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index d674c89..f73d1ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -23,15 +23,16 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -40,7 +41,6 @@
import com.google.common.truth.Truth.assertThat
import kotlin.math.max
import kotlin.math.min
-import kotlin.reflect.KClass
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.runBlockingTest
@@ -81,9 +81,21 @@
whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
.thenReturn(RETURNED_BURN_IN_OFFSET)
- homeControlsQuickAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
- quickAccessWalletAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
- qrCodeScannerAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
+ homeControlsQuickAffordanceConfig =
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
+ ) {}
+ quickAccessWalletAffordanceConfig =
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ ) {}
+ qrCodeScannerAffordanceConfig =
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
+ ) {}
registry =
FakeKeyguardQuickAffordanceRegistry(
mapOf(
@@ -489,42 +501,42 @@
private suspend fun setUpQuickAffordanceModel(
position: KeyguardQuickAffordancePosition,
testConfig: TestConfig,
- ): KClass<out FakeKeyguardQuickAffordanceConfig> {
+ ): String {
val config =
when (position) {
KeyguardQuickAffordancePosition.BOTTOM_START -> homeControlsQuickAffordanceConfig
KeyguardQuickAffordancePosition.BOTTOM_END -> quickAccessWalletAffordanceConfig
}
- val state =
+ val lockScreenState =
if (testConfig.isVisible) {
if (testConfig.intent != null) {
- config.onClickedResult =
- KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+ config.onTriggeredResult =
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
intent = testConfig.intent,
canShowWhileLocked = testConfig.canShowWhileLocked,
)
}
- KeyguardQuickAffordanceConfig.State.Visible(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon = testConfig.icon ?: error("Icon is unexpectedly null!"),
- toggle =
+ activationState =
when (testConfig.isActivated) {
- true -> KeyguardQuickAffordanceToggleState.On
- false -> KeyguardQuickAffordanceToggleState.Off
- null -> KeyguardQuickAffordanceToggleState.NotSupported
+ true -> ActivationState.Active
+ false -> ActivationState.Inactive
+ null -> ActivationState.NotSupported
}
)
} else {
- KeyguardQuickAffordanceConfig.State.Hidden
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden
}
- config.setState(state)
- return config::class
+ config.setState(lockScreenState)
+ return config.key
}
private fun assertQuickAffordanceViewModel(
viewModel: KeyguardQuickAffordanceViewModel?,
testConfig: TestConfig,
- configKey: KClass<out FakeKeyguardQuickAffordanceConfig>,
+ configKey: String,
) {
checkNotNull(viewModel)
assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index 071604d..920801f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -31,7 +31,7 @@
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.media.dream.MediaDreamComplication
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.testing.FakeNotifPanelEvents
+import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.KeyguardBypassController
@@ -89,7 +89,7 @@
private lateinit var mediaHierarchyManager: MediaHierarchyManager
private lateinit var mediaFrame: ViewGroup
private val configurationController = FakeConfigurationController()
- private val notifPanelEvents = FakeNotifPanelEvents()
+ private val notifPanelEvents = ShadeExpansionStateManager()
private val settings = FakeSettings()
private lateinit var testableLooper: TestableLooper
private lateinit var fakeHandler: FakeHandler
@@ -346,7 +346,7 @@
@Test
fun isCurrentlyInGuidedTransformation_hostsVisible_expandImmediateEnabled_returnsFalse() {
- notifPanelEvents.changeExpandImmediate(expandImmediate = true)
+ notifPanelEvents.notifyExpandImmediateChange(true)
goToLockscreen()
enterGuidedTransformation()
whenever(lockHost.visible).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index fdeb3f5..ad19bc2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -45,6 +45,7 @@
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
+import com.android.systemui.temporarydisplay.chipbar.ChipbarLogger
import com.android.systemui.temporarydisplay.chipbar.FakeChipbarCoordinator
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -80,6 +81,7 @@
@Mock private lateinit var configurationController: ConfigurationController
@Mock private lateinit var falsingManager: FalsingManager
@Mock private lateinit var falsingCollector: FalsingCollector
+ @Mock private lateinit var chipbarLogger: ChipbarLogger
@Mock private lateinit var logger: MediaTttLogger
@Mock private lateinit var mediaTttFlags: MediaTttFlags
@Mock private lateinit var packageManager: PackageManager
@@ -122,7 +124,7 @@
chipbarCoordinator =
FakeChipbarCoordinator(
context,
- logger,
+ chipbarLogger,
windowManager,
fakeExecutor,
accessibilityManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 02f28a2..ac4dd49 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -438,8 +438,6 @@
when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
mMainHandler = new Handler(Looper.getMainLooper());
- NotificationPanelViewController.PanelEventsEmitter panelEventsEmitter =
- new NotificationPanelViewController.PanelEventsEmitter();
mNotificationPanelViewController = new NotificationPanelViewController(
mView,
@@ -495,7 +493,6 @@
() -> mKeyguardBottomAreaViewController,
mKeyguardUnlockAnimationController,
mNotificationListContainer,
- panelEventsEmitter,
mNotificationStackSizeCalculator,
mUnlockedScreenOffAnimationController,
mShadeTransitionController,
@@ -1597,7 +1594,7 @@
mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
- verify(mUpdateMonitor).requestFaceAuth(true,
+ verify(mUpdateMonitor).requestFaceAuth(
FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED);
}
@@ -1607,7 +1604,7 @@
mNotificationPanelViewController.mStatusBarStateListener;
statusBarStateListener.onStateChanged(KEYGUARD);
mNotificationPanelViewController.setDozing(false, false);
- when(mUpdateMonitor.requestFaceAuth(true, NOTIFICATION_PANEL_CLICKED)).thenReturn(false);
+ when(mUpdateMonitor.requestFaceAuth(NOTIFICATION_PANEL_CLICKED)).thenReturn(false);
// This sets the dozing state that is read when onMiddleClicked is eventually invoked.
mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
@@ -1622,7 +1619,7 @@
mNotificationPanelViewController.mStatusBarStateListener;
statusBarStateListener.onStateChanged(KEYGUARD);
mNotificationPanelViewController.setDozing(false, false);
- when(mUpdateMonitor.requestFaceAuth(true, NOTIFICATION_PANEL_CLICKED)).thenReturn(true);
+ when(mUpdateMonitor.requestFaceAuth(NOTIFICATION_PANEL_CLICKED)).thenReturn(true);
// This sets the dozing state that is read when onMiddleClicked is eventually invoked.
mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
@@ -1642,7 +1639,7 @@
mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
- verify(mUpdateMonitor, never()).requestFaceAuth(anyBoolean(), anyString());
+ verify(mUpdateMonitor, never()).requestFaceAuth(anyString());
}
@Test
@@ -1653,7 +1650,7 @@
mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
- verify(mUpdateMonitor, never()).requestFaceAuth(anyBoolean(), anyString());
+ verify(mUpdateMonitor, never()).requestFaceAuth(anyString());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/testing/FakeNotifPanelEvents.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/testing/FakeNotifPanelEvents.kt
deleted file mode 100644
index d052138..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/testing/FakeNotifPanelEvents.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2022 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.shade.testing
-
-import com.android.systemui.shade.NotifPanelEvents
-
-/** Fake implementation of [NotifPanelEvents] for testing. */
-class FakeNotifPanelEvents : NotifPanelEvents {
-
- private val listeners = mutableListOf<NotifPanelEvents.Listener>()
-
- override fun registerListener(listener: NotifPanelEvents.Listener) {
- listeners.add(listener)
- }
-
- override fun unregisterListener(listener: NotifPanelEvents.Listener) {
- listeners.remove(listener)
- }
-
- fun changeExpandImmediate(expandImmediate: Boolean) {
- listeners.forEach { it.onExpandImmediateChanged(expandImmediate) }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index c961cec..b4a5f5c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -36,7 +36,8 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.NotifPanelEvents;
+import com.android.systemui.shade.ShadeStateEvents;
+import com.android.systemui.shade.ShadeStateEvents.ShadeStateEventsListener;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -71,12 +72,12 @@
@Mock private StatusBarStateController mStatusBarStateController;
@Mock private Pluggable.PluggableListener<NotifStabilityManager> mInvalidateListener;
@Mock private HeadsUpManager mHeadsUpManager;
- @Mock private NotifPanelEvents mNotifPanelEvents;
+ @Mock private ShadeStateEvents mShadeStateEvents;
@Mock private VisualStabilityProvider mVisualStabilityProvider;
@Captor private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefulnessObserverCaptor;
@Captor private ArgumentCaptor<StatusBarStateController.StateListener> mSBStateListenerCaptor;
- @Captor private ArgumentCaptor<NotifPanelEvents.Listener> mNotifPanelEventsCallbackCaptor;
+ @Captor private ArgumentCaptor<ShadeStateEventsListener> mNotifPanelEventsCallbackCaptor;
@Captor private ArgumentCaptor<NotifStabilityManager> mNotifStabilityManagerCaptor;
private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
@@ -84,7 +85,7 @@
private WakefulnessLifecycle.Observer mWakefulnessObserver;
private StatusBarStateController.StateListener mStatusBarStateListener;
- private NotifPanelEvents.Listener mNotifPanelEventsCallback;
+ private ShadeStateEvents.ShadeStateEventsListener mNotifPanelEventsCallback;
private NotifStabilityManager mNotifStabilityManager;
private NotificationEntry mEntry;
private GroupEntry mGroupEntry;
@@ -97,7 +98,7 @@
mFakeExecutor,
mDumpManager,
mHeadsUpManager,
- mNotifPanelEvents,
+ mShadeStateEvents,
mStatusBarStateController,
mVisualStabilityProvider,
mWakefulnessLifecycle);
@@ -111,7 +112,8 @@
verify(mStatusBarStateController).addCallback(mSBStateListenerCaptor.capture());
mStatusBarStateListener = mSBStateListenerCaptor.getValue();
- verify(mNotifPanelEvents).registerListener(mNotifPanelEventsCallbackCaptor.capture());
+ verify(mShadeStateEvents).addShadeStateEventsListener(
+ mNotifPanelEventsCallbackCaptor.capture());
mNotifPanelEventsCallback = mNotifPanelEventsCallbackCaptor.getValue();
verify(mNotifPipeline).setVisualStabilityManager(mNotifStabilityManagerCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 91aecd8..dceb4ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -78,6 +78,7 @@
import org.junit.Assert;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -89,6 +90,7 @@
/**
* Tests for {@link NotificationStackScrollLayout}.
*/
+@Ignore("b/255552856")
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index 6ff7b7c..de1fec8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -24,7 +24,14 @@
private val _subscriptionsModelFlow = MutableStateFlow(MobileSubscriptionModel())
override val subscriptionModelFlow: Flow<MobileSubscriptionModel> = _subscriptionsModelFlow
+ private val _dataEnabled = MutableStateFlow(true)
+ override val dataEnabled = _dataEnabled
+
fun setMobileSubscriptionModel(model: MobileSubscriptionModel) {
_subscriptionsModelFlow.value = model
}
+
+ fun setDataEnabled(enabled: Boolean) {
+ _dataEnabled.value = enabled
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index c88d468..813e750 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -50,7 +50,7 @@
_activeMobileDataSubscriptionId.value = subId
}
- fun setMobileConnectionRepositoryForId(subId: Int, repo: MobileConnectionRepository) {
- subIdRepos[subId] = repo
+ fun setMobileConnectionRepositoryMap(connections: Map<Int, MobileConnectionRepository>) {
+ connections.forEach { entry -> subIdRepos[entry.key] = entry.value }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
index 775e6db..0939364 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
@@ -20,16 +20,20 @@
import android.telephony.ServiceState
import android.telephony.SignalStrength
import android.telephony.SubscriptionInfo
-import android.telephony.SubscriptionManager
import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.ServiceStateListener
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA
import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.DATA_CONNECTED
+import android.telephony.TelephonyManager.DATA_CONNECTING
+import android.telephony.TelephonyManager.DATA_DISCONNECTED
+import android.telephony.TelephonyManager.DATA_DISCONNECTING
import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
@@ -59,7 +63,6 @@
class MobileConnectionRepositoryTest : SysuiTestCase() {
private lateinit var underTest: MobileConnectionRepositoryImpl
- @Mock private lateinit var subscriptionManager: SubscriptionManager
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: ConnectivityPipelineLogger
@@ -148,16 +151,61 @@
}
@Test
- fun testFlowForSubId_dataConnectionState() =
+ fun testFlowForSubId_dataConnectionState_connected() =
runBlocking(IMMEDIATE) {
var latest: MobileSubscriptionModel? = null
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
- callback.onDataConnectionStateChanged(100, 200 /* unused */)
+ callback.onDataConnectionStateChanged(DATA_CONNECTED, 200 /* unused */)
- assertThat(latest?.dataConnectionState).isEqualTo(100)
+ assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Connected)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_dataConnectionState_connecting() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+ val callback =
+ getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(DATA_CONNECTING, 200 /* unused */)
+
+ assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Connecting)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_dataConnectionState_disconnected() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+ val callback =
+ getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(DATA_DISCONNECTED, 200 /* unused */)
+
+ assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Disconnected)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_dataConnectionState_disconnecting() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+ val callback =
+ getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(DATA_DISCONNECTING, 200 /* unused */)
+
+ assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Disconnecting)
job.cancel()
}
@@ -241,6 +289,32 @@
job.cancel()
}
+ @Test
+ fun dataEnabled_isEnabled() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true)
+
+ var latest: Boolean? = null
+ val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun dataEnabled_isDisabled() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
+
+ var latest: Boolean? = null
+ val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
private fun getTelephonyCallbacks(): List<TelephonyCallback> {
val callbackCaptor = argumentCaptor<TelephonyCallback>()
Mockito.verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index cd4dbeb..5611c44 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -22,19 +22,22 @@
import kotlinx.coroutines.flow.MutableStateFlow
class FakeMobileIconInteractor : MobileIconInteractor {
- private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.UNKNOWN)
+ private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.THREE_G)
override val networkTypeIconGroup = _iconGroup
- private val _isEmergencyOnly = MutableStateFlow<Boolean>(false)
+ private val _isEmergencyOnly = MutableStateFlow(false)
override val isEmergencyOnly = _isEmergencyOnly
- private val _level = MutableStateFlow<Int>(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+ private val _isDataEnabled = MutableStateFlow(true)
+ override val isDataEnabled = _isDataEnabled
+
+ private val _level = MutableStateFlow(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
override val level = _level
- private val _numberOfLevels = MutableStateFlow<Int>(4)
+ private val _numberOfLevels = MutableStateFlow(4)
override val numberOfLevels = _numberOfLevels
- private val _cutOut = MutableStateFlow<Boolean>(false)
+ private val _cutOut = MutableStateFlow(false)
override val cutOut = _cutOut
fun setIconGroup(group: SignalIcon.MobileIconGroup) {
@@ -45,6 +48,10 @@
_isEmergencyOnly.value = emergency
}
+ fun setIsDataEnabled(enabled: Boolean) {
+ _isDataEnabled.value = enabled
+ }
+
fun setLevel(level: Int) {
_level.value = level
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index b01efd1..877ce0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -19,6 +19,7 @@
import android.telephony.SubscriptionInfo
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
@@ -41,7 +42,7 @@
class MobileIconsInteractorTest : SysuiTestCase() {
private lateinit var underTest: MobileIconsInteractor
private val userSetupRepository = FakeUserSetupRepository()
- private val subscriptionsRepository = FakeMobileConnectionsRepository()
+ private val connectionsRepository = FakeMobileConnectionsRepository()
private val mobileMappingsProxy = FakeMobileMappingsProxy()
private val scope = CoroutineScope(IMMEDIATE)
@@ -50,9 +51,20 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+
+ connectionsRepository.setMobileConnectionRepositoryMap(
+ mapOf(
+ SUB_1_ID to CONNECTION_1,
+ SUB_2_ID to CONNECTION_2,
+ SUB_3_ID to CONNECTION_3,
+ SUB_4_ID to CONNECTION_4,
+ )
+ )
+ connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
+
underTest =
MobileIconsInteractorImpl(
- subscriptionsRepository,
+ connectionsRepository,
carrierConfigTracker,
mobileMappingsProxy,
userSetupRepository,
@@ -76,7 +88,7 @@
@Test
fun filteredSubscriptions_nonOpportunistic_updatesWithMultipleSubs() =
runBlocking(IMMEDIATE) {
- subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
+ connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
var latest: List<SubscriptionInfo>? = null
val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
@@ -89,8 +101,8 @@
@Test
fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_3() =
runBlocking(IMMEDIATE) {
- subscriptionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
- subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+ connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
+ connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(false)
@@ -106,8 +118,8 @@
@Test
fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_4() =
runBlocking(IMMEDIATE) {
- subscriptionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
- subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID)
+ connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
+ connectionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID)
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(false)
@@ -123,8 +135,8 @@
@Test
fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_active_1() =
runBlocking(IMMEDIATE) {
- subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
- subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
+ connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
+ connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(true)
@@ -141,8 +153,8 @@
@Test
fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_nonActive_1() =
runBlocking(IMMEDIATE) {
- subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
- subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+ connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
+ connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(true)
@@ -162,10 +174,12 @@
private const val SUB_1_ID = 1
private val SUB_1 =
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+ private val CONNECTION_1 = FakeMobileConnectionRepository()
private const val SUB_2_ID = 2
private val SUB_2 =
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+ private val CONNECTION_2 = FakeMobileConnectionRepository()
private const val SUB_3_ID = 3
private val SUB_3_OPP =
@@ -173,6 +187,7 @@
whenever(it.subscriptionId).thenReturn(SUB_3_ID)
whenever(it.isOpportunistic).thenReturn(true)
}
+ private val CONNECTION_3 = FakeMobileConnectionRepository()
private const val SUB_4_ID = 4
private val SUB_4_OPP =
@@ -180,5 +195,6 @@
whenever(it.subscriptionId).thenReturn(SUB_4_ID)
whenever(it.isOpportunistic).thenReturn(true)
}
+ private val CONNECTION_4 = FakeMobileConnectionRepository()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index b374abb..ce0f33f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -18,8 +18,10 @@
import androidx.test.filters.SmallTest
import com.android.settingslib.graph.SignalDrawable
-import com.android.settingslib.mobile.TelephonyIcons
+import com.android.settingslib.mobile.TelephonyIcons.THREE_G
import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.google.common.truth.Truth.assertThat
@@ -27,6 +29,7 @@
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
@@ -44,7 +47,7 @@
interactor.apply {
setLevel(1)
setCutOut(false)
- setIconGroup(TelephonyIcons.THREE_G)
+ setIconGroup(THREE_G)
setIsEmergencyOnly(false)
setNumberOfLevels(4)
}
@@ -62,6 +65,60 @@
job.cancel()
}
+ @Test
+ fun networkType_dataEnabled_groupIsRepresented() =
+ runBlocking(IMMEDIATE) {
+ val expected =
+ Icon.Resource(
+ THREE_G.dataType,
+ ContentDescription.Resource(THREE_G.dataContentDescription)
+ )
+ interactor.setIconGroup(THREE_G)
+
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(expected)
+
+ job.cancel()
+ }
+
+ @Test
+ fun networkType_nullWhenDisabled() =
+ runBlocking(IMMEDIATE) {
+ interactor.setIconGroup(THREE_G)
+ interactor.setIsDataEnabled(false)
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun networkType_null_changeToDisabled() =
+ runBlocking(IMMEDIATE) {
+ val expected =
+ Icon.Resource(
+ THREE_G.dataType,
+ ContentDescription.Resource(THREE_G.dataContentDescription)
+ )
+ interactor.setIconGroup(THREE_G)
+ interactor.setIsDataEnabled(true)
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(expected)
+
+ interactor.setIsDataEnabled(false)
+ yield()
+
+ assertThat(latest).isNull()
+
+ job.cancel()
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
private const val SUB_1_ID = 1
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
index b68eb88..91b5c35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
@@ -41,6 +41,7 @@
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -85,10 +86,29 @@
}
@Test
- fun displayView_viewAdded() {
- underTest.displayView(getState())
+ fun displayView_viewAddedWithCorrectTitle() {
+ underTest.displayView(
+ ViewInfo(
+ name = "name",
+ windowTitle = "Fake Window Title",
+ )
+ )
- verify(windowManager).addView(any(), any())
+ val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+ verify(windowManager).addView(any(), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value!!.title).isEqualTo("Fake Window Title")
+ }
+
+ @Test
+ fun displayView_logged() {
+ underTest.displayView(
+ ViewInfo(
+ name = "name",
+ windowTitle = "Fake Window Title",
+ )
+ )
+
+ verify(logger).logViewAddition("Fake Window Title")
}
@Test
@@ -110,7 +130,7 @@
}
@Test
- fun displayView_twice_viewNotAddedTwice() {
+ fun displayView_twiceWithSameWindowTitle_viewNotAddedTwice() {
underTest.displayView(getState())
reset(windowManager)
@@ -119,6 +139,32 @@
}
@Test
+ fun displayView_twiceWithDifferentWindowTitles_oldViewRemovedNewViewAdded() {
+ underTest.displayView(
+ ViewInfo(
+ name = "name",
+ windowTitle = "First Fake Window Title",
+ )
+ )
+
+ underTest.displayView(
+ ViewInfo(
+ name = "name",
+ windowTitle = "Second Fake Window Title",
+ )
+ )
+
+ val viewCaptor = argumentCaptor<View>()
+ val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+
+ verify(windowManager, times(2)).addView(capture(viewCaptor), capture(windowParamsCaptor))
+
+ assertThat(windowParamsCaptor.allValues[0].title).isEqualTo("First Fake Window Title")
+ assertThat(windowParamsCaptor.allValues[1].title).isEqualTo("Second Fake Window Title")
+ verify(windowManager).removeView(viewCaptor.allValues[0])
+ }
+
+ @Test
fun displayView_viewDoesNotDisappearsBeforeTimeout() {
val state = getState()
underTest.displayView(state)
@@ -197,7 +243,7 @@
underTest.removeView(reason)
verify(windowManager).removeView(any())
- verify(logger).logChipRemoval(reason)
+ verify(logger).logViewRemoval(reason)
}
@Test
@@ -232,8 +278,6 @@
configurationController,
powerManager,
R.layout.chipbar,
- "Window Title",
- "WAKE_REASON",
) {
var mostRecentViewInfo: ViewInfo? = null
@@ -250,9 +294,12 @@
}
}
- inner class ViewInfo(val name: String) : TemporaryViewInfo {
- override fun getTimeoutMs() = 1L
- }
+ inner class ViewInfo(
+ val name: String,
+ override val windowTitle: String = "Window Title",
+ override val wakeReason: String = "WAKE_REASON",
+ override val timeoutMs: Int = 1
+ ) : TemporaryViewInfo()
}
private const val TIMEOUT_MS = 10000L
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
index 13e9f60..d155050 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
@@ -43,20 +43,21 @@
}
@Test
- fun logChipAddition_bufferHasLog() {
- logger.logChipAddition()
+ fun logViewAddition_bufferHasLog() {
+ logger.logViewAddition("Test Window Title")
val stringWriter = StringWriter()
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
val actualString = stringWriter.toString()
assertThat(actualString).contains(TAG)
+ assertThat(actualString).contains("Test Window Title")
}
@Test
- fun logChipRemoval_bufferHasTagAndReason() {
+ fun logViewRemoval_bufferHasTagAndReason() {
val reason = "test reason"
- logger.logChipRemoval(reason)
+ logger.logViewRemoval(reason)
val stringWriter = StringWriter()
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
index 9fbf159..f643973 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -35,12 +35,12 @@
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.view.ViewUtil
import com.google.common.truth.Truth.assertThat
@@ -60,7 +60,7 @@
class ChipbarCoordinatorTest : SysuiTestCase() {
private lateinit var underTest: FakeChipbarCoordinator
- @Mock private lateinit var logger: MediaTttLogger
+ @Mock private lateinit var logger: ChipbarLogger
@Mock private lateinit var accessibilityManager: AccessibilityManager
@Mock private lateinit var configurationController: ConfigurationController
@Mock private lateinit var powerManager: PowerManager
@@ -105,7 +105,7 @@
val drawable = context.getDrawable(R.drawable.ic_celebration)!!
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Loaded(drawable, contentDescription = ContentDescription.Loaded("loadedCD")),
Text.Loaded("text"),
endItem = null,
@@ -121,7 +121,7 @@
fun displayView_resourceIcon_correctlyRendered() {
val contentDescription = ContentDescription.Resource(R.string.controls_error_timeout)
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.drawable.ic_cake, contentDescription),
Text.Loaded("text"),
endItem = null,
@@ -136,7 +136,7 @@
@Test
fun displayView_loadedText_correctlyRendered() {
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("display view text here"),
endItem = null,
@@ -149,7 +149,7 @@
@Test
fun displayView_resourceText_correctlyRendered() {
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Resource(R.string.screenrecord_start_error),
endItem = null,
@@ -163,7 +163,7 @@
@Test
fun displayView_endItemNull_correctlyRendered() {
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("text"),
endItem = null,
@@ -179,7 +179,7 @@
@Test
fun displayView_endItemLoading_correctlyRendered() {
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("text"),
endItem = ChipbarEndItem.Loading,
@@ -195,7 +195,7 @@
@Test
fun displayView_endItemError_correctlyRendered() {
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("text"),
endItem = ChipbarEndItem.Error,
@@ -211,7 +211,7 @@
@Test
fun displayView_endItemButton_correctlyRendered() {
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("text"),
endItem =
@@ -237,7 +237,7 @@
val buttonClickListener = View.OnClickListener { isClicked = true }
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("text"),
endItem =
@@ -260,7 +260,7 @@
val buttonClickListener = View.OnClickListener { isClicked = true }
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("text"),
endItem =
@@ -279,7 +279,7 @@
@Test
fun displayView_vibrationEffect_doubleClickEffect() {
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("text"),
endItem = null,
@@ -296,7 +296,7 @@
val drawable = context.getDrawable(R.drawable.ic_celebration)!!
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Loaded(drawable, contentDescription = ContentDescription.Loaded("loadedCD")),
Text.Loaded("title text"),
endItem = ChipbarEndItem.Loading,
@@ -314,7 +314,7 @@
// WHEN the view is updated
val newDrawable = context.getDrawable(R.drawable.ic_cake)!!
underTest.updateView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Loaded(newDrawable, ContentDescription.Loaded("new CD")),
Text.Loaded("new title text"),
endItem = ChipbarEndItem.Error,
@@ -331,6 +331,47 @@
assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE)
}
+ @Test
+ fun viewUpdates_logged() {
+ val drawable = context.getDrawable(R.drawable.ic_celebration)!!
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Loaded(drawable, contentDescription = ContentDescription.Loaded("loadedCD")),
+ Text.Loaded("title text"),
+ endItem = ChipbarEndItem.Loading,
+ )
+ )
+
+ verify(logger).logViewUpdate(eq(WINDOW_TITLE), eq("title text"), any())
+
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Loaded(drawable, ContentDescription.Loaded("new CD")),
+ Text.Loaded("new title text"),
+ endItem = ChipbarEndItem.Error,
+ )
+ )
+
+ verify(logger).logViewUpdate(eq(WINDOW_TITLE), eq("new title text"), any())
+ }
+
+ private fun createChipbarInfo(
+ startIcon: Icon,
+ text: Text,
+ endItem: ChipbarEndItem?,
+ vibrationEffect: VibrationEffect? = null,
+ ): ChipbarInfo {
+ return ChipbarInfo(
+ startIcon,
+ text,
+ endItem,
+ vibrationEffect,
+ windowTitle = WINDOW_TITLE,
+ wakeReason = WAKE_REASON,
+ timeoutMs = TIMEOUT,
+ )
+ }
+
private fun ViewGroup.getStartIconView() = this.requireViewById<ImageView>(R.id.start_icon)
private fun ViewGroup.getChipText(): String =
@@ -350,3 +391,5 @@
}
private const val TIMEOUT = 10000
+private const val WINDOW_TITLE = "Test Chipbar Window Title"
+private const val WAKE_REASON = "TEST_CHIPBAR_WAKE_REASON"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
index 17d4023..574f70e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
@@ -22,8 +22,6 @@
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger
-import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -33,7 +31,7 @@
/** A fake implementation of [ChipbarCoordinator] for testing. */
class FakeChipbarCoordinator(
context: Context,
- @MediaTttReceiverLogger logger: MediaTttLogger,
+ logger: ChipbarLogger,
windowManager: WindowManager,
mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
new file mode 100644
index 0000000..96658c6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
@@ -0,0 +1,48 @@
+package com.android.systemui.biometrics.data.repository
+
+import android.hardware.biometrics.PromptInfo
+import com.android.systemui.biometrics.data.model.PromptKind
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Fake implementation of [PromptRepository] for tests. */
+class FakePromptRepository : PromptRepository {
+
+ private val _isShowing = MutableStateFlow(false)
+ override val isShowing = _isShowing.asStateFlow()
+
+ private val _promptInfo = MutableStateFlow<PromptInfo?>(null)
+ override val promptInfo = _promptInfo.asStateFlow()
+
+ private val _userId = MutableStateFlow<Int?>(null)
+ override val userId = _userId.asStateFlow()
+
+ private var _challenge = MutableStateFlow<Long?>(null)
+ override val challenge = _challenge.asStateFlow()
+
+ private val _kind = MutableStateFlow(PromptKind.ANY_BIOMETRIC)
+ override val kind = _kind.asStateFlow()
+
+ override fun setPrompt(
+ promptInfo: PromptInfo,
+ userId: Int,
+ gatekeeperChallenge: Long?,
+ kind: PromptKind
+ ) {
+ _promptInfo.value = promptInfo
+ _userId.value = userId
+ _challenge.value = gatekeeperChallenge
+ _kind.value = kind
+ }
+
+ override fun unsetPrompt() {
+ _promptInfo.value = null
+ _userId.value = null
+ _challenge.value = null
+ _kind.value = PromptKind.ANY_BIOMETRIC
+ }
+
+ fun setIsShowing(showing: Boolean) {
+ _isShowing.value = showing
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FakeCredentialInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FakeCredentialInteractor.kt
new file mode 100644
index 0000000..fbe291e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FakeCredentialInteractor.kt
@@ -0,0 +1,31 @@
+package com.android.systemui.biometrics.domain.interactor
+
+import com.android.internal.widget.LockscreenCredential
+import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/** Fake implementation of [CredentialInteractor] for tests. */
+class FakeCredentialInteractor : CredentialInteractor {
+
+ /** Sets return value for [isStealthModeActive]. */
+ var stealthMode: Boolean = false
+
+ /** Sets return value for [getCredentialOwnerOrSelfId]. */
+ var credentialOwnerId: Int? = null
+
+ override fun isStealthModeActive(userId: Int): Boolean = stealthMode
+
+ override fun getCredentialOwnerOrSelfId(userId: Int): Int = credentialOwnerId ?: userId
+
+ override fun verifyCredential(
+ request: BiometricPromptRequest.Credential,
+ credential: LockscreenCredential,
+ ): Flow<CredentialStatus> = verifyCredentialResponse(credential)
+
+ /** Sets the result value for [verifyCredential]. */
+ var verifyCredentialResponse: (credential: LockscreenCredential) -> Flow<CredentialStatus> =
+ { _ ->
+ flowOf(CredentialStatus.Fail.Error("invalid"))
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index fc628cf..000bafe 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -44,6 +44,7 @@
import android.window.DisplayWindowPolicyController;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.BlockedAppStreamingActivity;
import java.util.List;
@@ -112,7 +113,9 @@
final ArraySet<Integer> mRunningUids = new ArraySet<>();
@Nullable private final ActivityListener mActivityListener;
private final Handler mHandler = new Handler(Looper.getMainLooper());
- private final ArraySet<RunningAppsChangedListener> mRunningAppsChangedListener =
+ @NonNull
+ @GuardedBy("mGenericWindowPolicyControllerLock")
+ private final ArraySet<RunningAppsChangedListener> mRunningAppsChangedListeners =
new ArraySet<>();
@Nullable
private final @AssociationRequest.DeviceProfile String mDeviceProfile;
@@ -178,12 +181,16 @@
/** Register a listener for running applications changes. */
public void registerRunningAppsChangedListener(@NonNull RunningAppsChangedListener listener) {
- mRunningAppsChangedListener.add(listener);
+ synchronized (mGenericWindowPolicyControllerLock) {
+ mRunningAppsChangedListeners.add(listener);
+ }
}
/** Unregister a listener for running applications changes. */
public void unregisterRunningAppsChangedListener(@NonNull RunningAppsChangedListener listener) {
- mRunningAppsChangedListener.remove(listener);
+ synchronized (mGenericWindowPolicyControllerLock) {
+ mRunningAppsChangedListeners.remove(listener);
+ }
}
@Override
@@ -283,12 +290,16 @@
// Post callback on the main thread so it doesn't block activity launching
mHandler.post(() -> mActivityListener.onDisplayEmpty(mDisplayId));
}
- }
- mHandler.post(() -> {
- for (RunningAppsChangedListener listener : mRunningAppsChangedListener) {
- listener.onRunningAppsChanged(runningUids);
+ if (!mRunningAppsChangedListeners.isEmpty()) {
+ final ArraySet<RunningAppsChangedListener> listeners =
+ new ArraySet<>(mRunningAppsChangedListeners);
+ mHandler.post(() -> {
+ for (RunningAppsChangedListener listener : listeners) {
+ listener.onRunningAppsChanged(runningUids);
+ }
+ });
}
- });
+ }
}
@Override
@@ -354,4 +365,11 @@
}
return true;
}
+
+ @VisibleForTesting
+ int getRunningAppsChangedListenersSizeForTesting() {
+ synchronized (mGenericWindowPolicyControllerLock) {
+ return mRunningAppsChangedListeners.size();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c54d7d1..3032f17 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3891,24 +3891,29 @@
finishForceStopPackageLocked(packageName, appInfo.uid);
}
}
- final Intent intent = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED,
- Uri.fromParts("package", packageName, null));
- intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
- | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- intent.putExtra(Intent.EXTRA_UID, (appInfo != null) ? appInfo.uid : -1);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, resolvedUserId);
- final int[] visibilityAllowList =
- mPackageManagerInt.getVisibilityAllowList(packageName, resolvedUserId);
- if (isInstantApp) {
- intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
- broadcastIntentInPackage("android", null, SYSTEM_UID, uid, pid, intent,
- null, null, null, 0, null, null, permission.ACCESS_INSTANT_APPS,
- null, false, false, resolvedUserId, false, null,
- visibilityAllowList);
- } else {
- broadcastIntentInPackage("android", null, SYSTEM_UID, uid, pid, intent,
- null, null, null, 0, null, null, null, null, false, false,
- resolvedUserId, false, null, visibilityAllowList);
+
+ if (succeeded) {
+ final Intent intent = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED,
+ Uri.fromParts("package", packageName, null /* fragment */));
+ intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
+ | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(Intent.EXTRA_UID,
+ (appInfo != null) ? appInfo.uid : INVALID_UID);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, resolvedUserId);
+ if (isInstantApp) {
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+ }
+ final int[] visibilityAllowList = mPackageManagerInt.getVisibilityAllowList(
+ packageName, resolvedUserId);
+
+ broadcastIntentInPackage("android", null /* featureId */,
+ SYSTEM_UID, uid, pid, intent, null /* resolvedType */,
+ null /* resultToApp */, null /* resultTo */, 0 /* resultCode */,
+ null /* resultData */, null /* resultExtras */,
+ isInstantApp ? permission.ACCESS_INSTANT_APPS : null,
+ null /* bOptions */, false /* serialized */, false /* sticky */,
+ resolvedUserId, false /* allowBackgroundActivityStarts */,
+ null /* backgroundActivityStartsToken */, visibilityAllowList);
}
if (observer != null) {
@@ -13860,6 +13865,9 @@
@Nullable IBinder backgroundActivityStartsToken,
@Nullable int[] broadcastAllowList,
@Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
+ // Ensure all internal loopers are registered for idle checks
+ BroadcastLoopers.addMyLooper();
+
if ((resultTo != null) && (resultToApp == null)) {
if (resultTo.asBinder() instanceof BinderProxy) {
// Warn when requesting results without a way to deliver them
@@ -18110,6 +18118,7 @@
public void waitForBroadcastIdle(@Nullable PrintWriter pw) {
enforceCallingPermission(permission.DUMP, "waitForBroadcastIdle()");
+ BroadcastLoopers.waitForIdle(pw);
for (BroadcastQueue queue : mBroadcastQueues) {
queue.waitForIdle(pw);
}
@@ -18121,6 +18130,7 @@
public void waitForBroadcastBarrier(@Nullable PrintWriter pw) {
enforceCallingPermission(permission.DUMP, "waitForBroadcastBarrier()");
+ BroadcastLoopers.waitForIdle(pw);
for (BroadcastQueue queue : mBroadcastQueues) {
queue.waitForBarrier(pw);
}
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 4590c85..417a0e5 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -133,7 +133,7 @@
*/
public boolean MODERN_QUEUE_ENABLED = DEFAULT_MODERN_QUEUE_ENABLED;
private static final String KEY_MODERN_QUEUE_ENABLED = "modern_queue_enabled";
- private static final boolean DEFAULT_MODERN_QUEUE_ENABLED = false;
+ private static final boolean DEFAULT_MODERN_QUEUE_ENABLED = true;
/**
* For {@link BroadcastQueueModernImpl}: Maximum number of process queues to
diff --git a/services/core/java/com/android/server/am/BroadcastLoopers.java b/services/core/java/com/android/server/am/BroadcastLoopers.java
new file mode 100644
index 0000000..bebb484
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastLoopers.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 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.am;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
+import android.os.SystemClock;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Collection of {@link Looper} that are known to be used for broadcast dispatch
+ * within the system. This collection can be useful for callers interested in
+ * confirming that all pending broadcasts have been successfully enqueued.
+ */
+public class BroadcastLoopers {
+ private static final String TAG = "BroadcastLoopers";
+
+ private static final ArraySet<Looper> sLoopers = new ArraySet<>();
+
+ /**
+ * Register the given {@link Looper} as possibly having messages that will
+ * dispatch broadcasts.
+ */
+ public static void addLooper(@NonNull Looper looper) {
+ synchronized (sLoopers) {
+ sLoopers.add(Objects.requireNonNull(looper));
+ }
+ }
+
+ /**
+ * If the current thread is hosting a {@link Looper}, then register it as
+ * possibly having messages that will dispatch broadcasts.
+ */
+ public static void addMyLooper() {
+ final Looper looper = Looper.myLooper();
+ if (looper != null) {
+ synchronized (sLoopers) {
+ if (sLoopers.add(looper)) {
+ Slog.w(TAG, "Found previously unknown looper " + looper.getThread());
+ }
+ }
+ }
+ }
+
+ /**
+ * Wait for all registered {@link Looper} instances to become idle, as
+ * defined by {@link MessageQueue#isIdle()}. Note that {@link Message#when}
+ * still in the future are ignored for the purposes of the idle test.
+ */
+ public static void waitForIdle(@Nullable PrintWriter pw) {
+ final CountDownLatch latch;
+ synchronized (sLoopers) {
+ final int N = sLoopers.size();
+ latch = new CountDownLatch(N);
+ for (int i = 0; i < N; i++) {
+ final MessageQueue queue = sLoopers.valueAt(i).getQueue();
+ if (queue.isIdle()) {
+ latch.countDown();
+ } else {
+ queue.addIdleHandler(() -> {
+ latch.countDown();
+ return false;
+ });
+ }
+ }
+ }
+
+ long lastPrint = 0;
+ while (latch.getCount() > 0) {
+ final long now = SystemClock.uptimeMillis();
+ if (now >= lastPrint + 1000) {
+ lastPrint = now;
+ logv("Waiting for " + latch.getCount() + " loopers to drain...", pw);
+ }
+ SystemClock.sleep(100);
+ }
+ logv("Loopers drained!", pw);
+ }
+
+ private static void logv(@NonNull String msg, @Nullable PrintWriter pw) {
+ Slog.v(TAG, msg);
+ if (pw != null) {
+ pw.println(msg);
+ pw.flush();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 9ec1fb4..f7d24e9 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -831,7 +831,7 @@
@NeverCompile
public void dumpLocked(@UptimeMillisLong long now, @NonNull IndentingPrintWriter pw) {
- if ((mActive == null) && mPending.isEmpty()) return;
+ if ((mActive == null) && isEmpty()) return;
pw.print(toShortString());
if (isRunnable()) {
@@ -847,6 +847,10 @@
if (mActive != null) {
dumpRecord(now, pw, mActive, mActiveIndex, mActiveBlockedUntilTerminalCount);
}
+ for (SomeArgs args : mPendingUrgent) {
+ final BroadcastRecord r = (BroadcastRecord) args.arg1;
+ dumpRecord(now, pw, r, args.argi1, args.argi2);
+ }
for (SomeArgs args : mPending) {
final BroadcastRecord r = (BroadcastRecord) args.arg1;
dumpRecord(now, pw, r, args.argi1, args.argi2);
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index ab19ae3..9e9eb71 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -365,6 +365,13 @@
BroadcastProcessQueue nextQueue = queue.runnableAtNext;
final long runnableAt = queue.getRunnableAt();
+ // When broadcasts are skipped or failed during list traversal, we
+ // might encounter a queue that is no longer runnable; skip it
+ if (!queue.isRunnable()) {
+ queue = nextQueue;
+ continue;
+ }
+
// If queues beyond this point aren't ready to run yet, schedule
// another pass when they'll be runnable
if (runnableAt > now && !waitingFor) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index c59ee83..bbffc89 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -344,7 +344,7 @@
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "setCommunicationRouteForClient: device: " + device);
}
- AudioService.sDeviceLogger.log((new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
"setCommunicationRouteForClient for pid: " + pid
+ " device: " + device
+ " from API: " + eventSource)).printLog(TAG));
@@ -1212,7 +1212,7 @@
if (useCase == AudioSystem.FOR_MEDIA) {
postReportNewRoutes(fromA2dp);
}
- AudioService.sForceUseLogger.log(
+ AudioService.sForceUseLogger.enqueue(
new AudioServiceEvents.ForceUseEvent(useCase, config, eventSource));
new MediaMetrics.Item(MediaMetrics.Name.AUDIO_FORCE_USE + MediaMetrics.SEPARATOR
+ AudioSystem.forceUseUsageToString(useCase))
@@ -1230,7 +1230,7 @@
}
private void onSendBecomingNoisyIntent() {
- AudioService.sDeviceLogger.log((new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
"broadcast ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG));
mSystemServer.sendDeviceBecomingNoisyIntent();
}
@@ -1468,7 +1468,7 @@
case MSG_L_BT_ACTIVE_DEVICE_CHANGE_EXT: {
final BtDeviceInfo info = (BtDeviceInfo) msg.obj;
if (info.mDevice == null) break;
- AudioService.sDeviceLogger.log((new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
"msg: onBluetoothActiveDeviceChange "
+ " state=" + info.mState
// only querying address as this is the only readily available
@@ -1858,7 +1858,7 @@
Log.v(TAG, "onUpdateCommunicationRoute, preferredCommunicationDevice: "
+ preferredCommunicationDevice + " eventSource: " + eventSource);
}
- AudioService.sDeviceLogger.log((new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
"onUpdateCommunicationRoute, preferredCommunicationDevice: "
+ preferredCommunicationDevice + " eventSource: " + eventSource)));
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index ce36ff8..c8f282f 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -309,7 +309,7 @@
address = "";
}
- AudioService.sDeviceLogger.log(new EventLogger.StringEvent("BT connected:"
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent("BT connected:"
+ " addr=" + address
+ " profile=" + btInfo.mProfile
+ " state=" + btInfo.mState
@@ -412,13 +412,13 @@
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
address = "";
}
- AudioService.sDeviceLogger.log(new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"onBluetoothA2dpDeviceConfigChange addr=" + address
+ " event=" + BtHelper.a2dpDeviceEventToString(event)));
synchronized (mDevicesLock) {
if (mDeviceBroker.hasScheduledA2dpConnection(btDevice)) {
- AudioService.sDeviceLogger.log(new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"A2dp config change ignored (scheduled connection change)")
.printLog(TAG));
mmi.set(MediaMetrics.Property.EARLY_RETURN, "A2dp config change ignored")
@@ -460,7 +460,7 @@
BtHelper.getName(btDevice), a2dpCodec);
if (res != AudioSystem.AUDIO_STATUS_OK) {
- AudioService.sDeviceLogger.log(new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"APM handleDeviceConfigChange failed for A2DP device addr=" + address
+ " codec=" + AudioSystem.audioFormatToString(a2dpCodec))
.printLog(TAG));
@@ -472,7 +472,7 @@
BluetoothProfile.A2DP, BluetoothProfile.STATE_DISCONNECTED,
musicDevice, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
} else {
- AudioService.sDeviceLogger.log(new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"APM handleDeviceConfigChange success for A2DP device addr=" + address
+ " codec=" + AudioSystem.audioFormatToString(a2dpCodec))
.printLog(TAG));
@@ -522,7 +522,7 @@
AudioDeviceInventory.WiredDeviceConnectionState wdcs) {
int type = wdcs.mAttributes.getInternalType();
- AudioService.sDeviceLogger.log(new AudioServiceEvents.WiredDevConnectEvent(wdcs));
+ AudioService.sDeviceLogger.enqueue(new AudioServiceEvents.WiredDevConnectEvent(wdcs));
MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId
+ "onSetWiredDeviceConnectionState")
@@ -619,7 +619,7 @@
@NonNull List<AudioDeviceAttributes> devices) {
final long identity = Binder.clearCallingIdentity();
- AudioService.sDeviceLogger.log((new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
"setPreferredDevicesForStrategySync, strategy: " + strategy
+ " devices: " + devices)).printLog(TAG));
final int status = mAudioSystem.setDevicesRoleForStrategy(
@@ -635,7 +635,7 @@
/*package*/ int removePreferredDevicesForStrategySync(int strategy) {
final long identity = Binder.clearCallingIdentity();
- AudioService.sDeviceLogger.log((new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
"removePreferredDevicesForStrategySync, strategy: "
+ strategy)).printLog(TAG));
@@ -1000,13 +1000,13 @@
// TODO: log in MediaMetrics once distinction between connection failure and
// double connection is made.
if (res != AudioSystem.AUDIO_STATUS_OK) {
- AudioService.sDeviceLogger.log(new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"APM failed to make available A2DP device addr=" + address
+ " error=" + res).printLog(TAG));
// TODO: connection failed, stop here
// TODO: return;
} else {
- AudioService.sDeviceLogger.log(new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"A2DP device addr=" + address + " now available").printLog(TAG));
}
@@ -1047,7 +1047,7 @@
if (!deviceToRemoveKey
.equals(mApmConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP))) {
// removing A2DP device not currently used by AudioPolicy, log but don't act on it
- AudioService.sDeviceLogger.log((new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
"A2DP device " + address + " made unavailable, was not used")).printLog(TAG));
mmi.set(MediaMetrics.Property.EARLY_RETURN,
"A2DP device made unavailable, was not used")
@@ -1062,13 +1062,13 @@
AudioSystem.DEVICE_STATE_UNAVAILABLE, a2dpCodec);
if (res != AudioSystem.AUDIO_STATUS_OK) {
- AudioService.sDeviceLogger.log(new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"APM failed to make unavailable A2DP device addr=" + address
+ " error=" + res).printLog(TAG));
// TODO: failed to disconnect, stop here
// TODO: return;
} else {
- AudioService.sDeviceLogger.log((new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
"A2DP device addr=" + address + " made unavailable")).printLog(TAG));
}
mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
@@ -1314,7 +1314,7 @@
&& !mDeviceBroker.hasAudioFocusUsers()) {
// no media playback, not a "becoming noisy" situation, otherwise it could cause
// the pausing of some apps that are playing remotely
- AudioService.sDeviceLogger.log((new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
"dropping ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG));
mmi.set(MediaMetrics.Property.DELAY_MS, 0).record(); // OK to return
return 0;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index f3a9a69..9d6fa9e 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -990,7 +990,7 @@
public AudioService(Context context, AudioSystemAdapter audioSystem,
SystemServerAdapter systemServer, SettingsAdapter settings, @Nullable Looper looper,
AppOpsManager appOps) {
- sLifecycleLogger.log(new EventLogger.StringEvent("AudioService()"));
+ sLifecycleLogger.enqueue(new EventLogger.StringEvent("AudioService()"));
mContext = context;
mContentResolver = context.getContentResolver();
mAppOps = appOps;
@@ -1539,14 +1539,14 @@
if (!mSystemReady ||
(AudioSystem.checkAudioFlinger() != AudioSystem.AUDIO_STATUS_OK)) {
Log.e(TAG, "Audioserver died.");
- sLifecycleLogger.log(new EventLogger.StringEvent(
+ sLifecycleLogger.enqueue(new EventLogger.StringEvent(
"onAudioServerDied() audioserver died"));
sendMsg(mAudioHandler, MSG_AUDIO_SERVER_DIED, SENDMSG_NOOP, 0, 0,
null, 500);
return;
}
Log.i(TAG, "Audioserver started.");
- sLifecycleLogger.log(new EventLogger.StringEvent(
+ sLifecycleLogger.enqueue(new EventLogger.StringEvent(
"onAudioServerDied() audioserver started"));
updateAudioHalPids();
@@ -1776,7 +1776,7 @@
// did it work? check based on status
if (status != AudioSystem.AUDIO_STATUS_OK) {
- sLifecycleLogger.log(new EventLogger.StringEvent(
+ sLifecycleLogger.enqueue(new EventLogger.StringEvent(
caller + ": initStreamVolume failed with " + status + " will retry")
.printLog(ALOGE, TAG));
sendMsg(mAudioHandler, MSG_REINIT_VOLUMES, SENDMSG_NOOP, 0, 0,
@@ -1790,7 +1790,7 @@
}
// success
- sLifecycleLogger.log(new EventLogger.StringEvent(
+ sLifecycleLogger.enqueue(new EventLogger.StringEvent(
caller + ": initStreamVolume succeeded").printLog(ALOGI, TAG));
}
@@ -1813,7 +1813,7 @@
}
}
if (!success) {
- sLifecycleLogger.log(new EventLogger.StringEvent(
+ sLifecycleLogger.enqueue(new EventLogger.StringEvent(
caller + ": initStreamVolume succeeded but invalid mix/max levels, will retry")
.printLog(ALOGW, TAG));
sendMsg(mAudioHandler, MSG_REINIT_VOLUMES, SENDMSG_NOOP, 0, 0,
@@ -2764,7 +2764,7 @@
"setPreferredDeviceForStrategy u/pid:%d/%d strat:%d dev:%s",
Binder.getCallingUid(), Binder.getCallingPid(), strategy,
devices.stream().map(e -> e.toString()).collect(Collectors.joining(",")));
- sDeviceLogger.log(new EventLogger.StringEvent(logString).printLog(TAG));
+ sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
if (devices.stream().anyMatch(device ->
device.getRole() == AudioDeviceAttributes.ROLE_INPUT)) {
Log.e(TAG, "Unsupported input routing in " + logString);
@@ -2784,7 +2784,7 @@
public int removePreferredDevicesForStrategy(int strategy) {
final String logString =
String.format("removePreferredDeviceForStrategy strat:%d", strategy);
- sDeviceLogger.log(new EventLogger.StringEvent(logString).printLog(TAG));
+ sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
final int status = mDeviceBroker.removePreferredDevicesForStrategySync(strategy);
if (status != AudioSystem.SUCCESS) {
@@ -2850,7 +2850,7 @@
"setPreferredDevicesForCapturePreset u/pid:%d/%d source:%d dev:%s",
Binder.getCallingUid(), Binder.getCallingPid(), capturePreset,
devices.stream().map(e -> e.toString()).collect(Collectors.joining(",")));
- sDeviceLogger.log(new EventLogger.StringEvent(logString).printLog(TAG));
+ sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
if (devices.stream().anyMatch(device ->
device.getRole() == AudioDeviceAttributes.ROLE_OUTPUT)) {
Log.e(TAG, "Unsupported output routing in " + logString);
@@ -2871,7 +2871,7 @@
public int clearPreferredDevicesForCapturePreset(int capturePreset) {
final String logString = String.format(
"removePreferredDeviceForCapturePreset source:%d", capturePreset);
- sDeviceLogger.log(new EventLogger.StringEvent(logString).printLog(TAG));
+ sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
final int status = mDeviceBroker.clearPreferredDevicesForCapturePresetSync(capturePreset);
if (status != AudioSystem.SUCCESS) {
@@ -3043,9 +3043,10 @@
+ ", volControlStream=" + mVolumeControlStream
+ ", userSelect=" + mUserSelectedVolumeControlStream);
if (direction != AudioManager.ADJUST_SAME) {
- sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_SUGG_VOL, suggestedStreamType,
- direction/*val1*/, flags/*val2*/, new StringBuilder(callingPackage)
- .append("/").append(caller).append(" uid:").append(uid).toString()));
+ sVolumeLogger.enqueue(
+ new VolumeEvent(VolumeEvent.VOL_ADJUST_SUGG_VOL, suggestedStreamType,
+ direction/*val1*/, flags/*val2*/, new StringBuilder(callingPackage)
+ .append("/").append(caller).append(" uid:").append(uid).toString()));
}
boolean hasExternalVolumeController = notifyExternalVolumeController(direction);
@@ -3144,7 +3145,7 @@
return;
}
- sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType,
+ sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType,
direction/*val1*/, flags/*val2*/, callingPackage));
adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage,
Binder.getCallingUid(), Binder.getCallingPid(), attributionTag,
@@ -3625,7 +3626,7 @@
}
final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
- sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, attr, vgs.name(),
+ sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, attr, vgs.name(),
index/*val1*/, flags/*val2*/, callingPackage));
vgs.setVolumeIndex(index, flags);
@@ -3776,7 +3777,7 @@
? new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
index/*val1*/, flags/*val2*/, callingPackage)
: new DeviceVolumeEvent(streamType, index, device, callingPackage);
- sVolumeLogger.log(event);
+ sVolumeLogger.enqueue(event);
setStreamVolume(streamType, index, flags, device,
callingPackage, callingPackage, attributionTag,
Binder.getCallingUid(), callingOrSelfHasAudioSettingsPermission());
@@ -3977,7 +3978,7 @@
private void updateHearingAidVolumeOnVoiceActivityUpdate() {
final int streamType = getBluetoothContextualVolumeStream();
final int index = getStreamVolume(streamType);
- sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_VOICE_ACTIVITY_HEARING_AID,
+ sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_VOICE_ACTIVITY_HEARING_AID,
mVoicePlaybackActive.get(), streamType, index));
mDeviceBroker.postSetHearingAidVolumeIndex(index * 10, streamType);
@@ -4018,7 +4019,7 @@
if (AudioSystem.isSingleAudioDeviceType(
absVolumeMultiModeCaseDevices, AudioSystem.DEVICE_OUT_HEARING_AID)) {
final int index = getStreamVolume(streamType);
- sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_MODE_CHANGE_HEARING_AID,
+ sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_MODE_CHANGE_HEARING_AID,
newMode, streamType, index));
mDeviceBroker.postSetHearingAidVolumeIndex(index * 10, streamType);
}
@@ -5419,7 +5420,7 @@
/*obj*/ null, /*delay*/ 0);
int previousMode = mMode.getAndSet(mode);
// Note: newModeOwnerPid is always 0 when actualMode is MODE_NORMAL
- mModeLogger.log(new PhoneStateEvent(requesterPackage, requesterPid,
+ mModeLogger.enqueue(new PhoneStateEvent(requesterPackage, requesterPid,
requestedMode, pid, mode));
int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
@@ -5562,7 +5563,7 @@
}
if (direction != AudioManager.ADJUST_SAME) {
- sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_VOL_UID, streamType,
+ sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_ADJUST_VOL_UID, streamType,
direction/*val1*/, flags/*val2*/,
new StringBuilder(packageName).append(" uid:").append(uid)
.toString()));
@@ -6892,7 +6893,7 @@
// verify arguments
Objects.requireNonNull(device);
AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior);
- sVolumeLogger.log(new EventLogger.StringEvent("setDeviceVolumeBehavior: dev:"
+ sVolumeLogger.enqueue(new EventLogger.StringEvent("setDeviceVolumeBehavior: dev:"
+ AudioSystem.getOutputDeviceName(device.getInternalType()) + " addr:"
+ device.getAddress() + " behavior:"
+ AudioDeviceVolumeManager.volumeBehaviorName(deviceVolumeBehavior)
@@ -6948,7 +6949,7 @@
}
// log event and caller
- sDeviceLogger.log(new EventLogger.StringEvent(
+ sDeviceLogger.enqueue(new EventLogger.StringEvent(
"Volume behavior " + deviceVolumeBehavior + " for dev=0x"
+ Integer.toHexString(audioSystemDeviceOut) + " from:" + caller));
// make sure we have a volume entry for this device, and that volume is updated according
@@ -7594,7 +7595,7 @@
final int status = AudioSystem.initStreamVolume(
streamType, mIndexMin / 10, mIndexMax / 10);
if (status != AudioSystem.AUDIO_STATUS_OK) {
- sLifecycleLogger.log(new EventLogger.StringEvent(
+ sLifecycleLogger.enqueue(new EventLogger.StringEvent(
"VSS() stream:" + streamType + " initStreamVolume=" + status)
.printLog(ALOGE, TAG));
sendMsg(mAudioHandler, MSG_REINIT_VOLUMES, SENDMSG_NOOP, 0, 0,
@@ -8013,7 +8014,7 @@
}
}
if (changed) {
- sVolumeLogger.log(new VolumeEvent(
+ sVolumeLogger.enqueue(new VolumeEvent(
VolumeEvent.VOL_MUTE_STREAM_INT, mStreamType, state));
}
return changed;
@@ -8183,10 +8184,10 @@
streamState.setIndex(index, update.mDevice, update.mCaller,
// trusted as index is always validated before message is posted
true /*hasModifyAudioSettings*/);
- sVolumeLogger.log(new EventLogger.StringEvent(update.mCaller + " dev:0x"
+ sVolumeLogger.enqueue(new EventLogger.StringEvent(update.mCaller + " dev:0x"
+ Integer.toHexString(update.mDevice) + " volIdx:" + index));
} else {
- sVolumeLogger.log(new EventLogger.StringEvent(update.mCaller
+ sVolumeLogger.enqueue(new EventLogger.StringEvent(update.mCaller
+ " update vol on dev:0x" + Integer.toHexString(update.mDevice)));
}
setDeviceVolume(streamState, update.mDevice);
@@ -8364,7 +8365,7 @@
.set(MediaMetrics.Property.FORCE_USE_MODE,
AudioSystem.forceUseConfigToString(config))
.record();
- sForceUseLogger.log(
+ sForceUseLogger.enqueue(
new AudioServiceEvents.ForceUseEvent(useCase, config, eventSource));
mAudioSystem.setForceUse(useCase, config);
}
@@ -8633,7 +8634,7 @@
private void avrcpSupportsAbsoluteVolume(String address, boolean support) {
// address is not used for now, but may be used when multiple a2dp devices are supported
- sVolumeLogger.log(new EventLogger.StringEvent("avrcpSupportsAbsoluteVolume addr="
+ sVolumeLogger.enqueue(new EventLogger.StringEvent("avrcpSupportsAbsoluteVolume addr="
+ address + " support=" + support).printLog(TAG));
mDeviceBroker.setAvrcpAbsoluteVolumeSupported(support);
setAvrcpAbsoluteVolumeSupported(support);
@@ -8767,13 +8768,21 @@
UserInfo userInfo = UserManagerService.getInstance().getUserInfo(userId);
killBackgroundUserProcessesWithRecordAudioPermission(userInfo);
}
- UserManagerService.getInstance().setUserRestriction(
- UserManager.DISALLOW_RECORD_AUDIO, true, userId);
+ try {
+ UserManagerService.getInstance().setUserRestriction(
+ UserManager.DISALLOW_RECORD_AUDIO, true, userId);
+ } catch (IllegalArgumentException e) {
+ Slog.w(TAG, "Failed to apply DISALLOW_RECORD_AUDIO restriction: " + e);
+ }
} else if (action.equals(Intent.ACTION_USER_FOREGROUND)) {
// Enable audio recording for foreground user/profile
int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
- UserManagerService.getInstance().setUserRestriction(
- UserManager.DISALLOW_RECORD_AUDIO, false, userId);
+ try {
+ UserManagerService.getInstance().setUserRestriction(
+ UserManager.DISALLOW_RECORD_AUDIO, false, userId);
+ } catch (IllegalArgumentException e) {
+ Slog.w(TAG, "Failed to apply DISALLOW_RECORD_AUDIO restriction: " + e);
+ }
} else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
if (state == BluetoothAdapter.STATE_OFF ||
@@ -10668,7 +10677,7 @@
pcb.asBinder().linkToDeath(app, 0/*flags*/);
// logging after registration so we have the registration id
- mDynPolicyLogger.log((new EventLogger.StringEvent("registerAudioPolicy for "
+ mDynPolicyLogger.enqueue((new EventLogger.StringEvent("registerAudioPolicy for "
+ pcb.asBinder() + " u/pid:" + Binder.getCallingUid() + "/"
+ Binder.getCallingPid() + " with config:" + app.toCompactLogString()))
.printLog(TAG));
@@ -10866,7 +10875,7 @@
private void unregisterAudioPolicyInt(@NonNull IAudioPolicyCallback pcb, String operationName) {
- mDynPolicyLogger.log((new EventLogger.StringEvent(operationName + " for "
+ mDynPolicyLogger.enqueue((new EventLogger.StringEvent(operationName + " for "
+ pcb.asBinder()).printLog(TAG)));
synchronized (mAudioPolicies) {
AudioPolicyProxy app = mAudioPolicies.remove(pcb.asBinder());
@@ -11394,7 +11403,7 @@
}
public void binderDied() {
- mDynPolicyLogger.log((new EventLogger.StringEvent("AudioPolicy "
+ mDynPolicyLogger.enqueue((new EventLogger.StringEvent("AudioPolicy "
+ mPolicyCallback.asBinder() + " died").printLog(TAG)));
release();
}
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 399829e..df65dbd 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -263,20 +263,20 @@
/*package*/ synchronized void setAvrcpAbsoluteVolumeIndex(int index) {
if (mA2dp == null) {
if (AudioService.DEBUG_VOL) {
- AudioService.sVolumeLogger.log(new EventLogger.StringEvent(
+ AudioService.sVolumeLogger.enqueue(new EventLogger.StringEvent(
"setAvrcpAbsoluteVolumeIndex: bailing due to null mA2dp").printLog(TAG));
return;
}
}
if (!mAvrcpAbsVolSupported) {
- AudioService.sVolumeLogger.log(new EventLogger.StringEvent(
+ AudioService.sVolumeLogger.enqueue(new EventLogger.StringEvent(
"setAvrcpAbsoluteVolumeIndex: abs vol not supported ").printLog(TAG));
return;
}
if (AudioService.DEBUG_VOL) {
Log.i(TAG, "setAvrcpAbsoluteVolumeIndex index=" + index);
}
- AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
+ AudioService.sVolumeLogger.enqueue(new AudioServiceEvents.VolumeEvent(
AudioServiceEvents.VolumeEvent.VOL_SET_AVRCP_VOL, index));
mA2dp.setAvrcpAbsoluteVolume(index);
}
@@ -393,14 +393,14 @@
@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
/*package*/ synchronized boolean startBluetoothSco(int scoAudioMode,
@NonNull String eventSource) {
- AudioService.sDeviceLogger.log(new EventLogger.StringEvent(eventSource));
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(eventSource));
return requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
}
// @GuardedBy("AudioDeviceBroker.mSetModeLock")
@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
/*package*/ synchronized boolean stopBluetoothSco(@NonNull String eventSource) {
- AudioService.sDeviceLogger.log(new EventLogger.StringEvent(eventSource));
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(eventSource));
return requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, SCO_MODE_VIRTUAL_CALL);
}
@@ -418,7 +418,7 @@
Log.i(TAG, "setLeAudioVolume: calling mLeAudio.setVolume idx="
+ index + " volume=" + volume);
}
- AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
+ AudioService.sVolumeLogger.enqueue(new AudioServiceEvents.VolumeEvent(
AudioServiceEvents.VolumeEvent.VOL_SET_LE_AUDIO_VOL, index, maxIndex));
mLeAudio.setVolume(volume);
}
@@ -443,7 +443,7 @@
}
// do not log when hearing aid is not connected to avoid confusion when reading dumpsys
if (isHeadAidConnected) {
- AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
+ AudioService.sVolumeLogger.enqueue(new AudioServiceEvents.VolumeEvent(
AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB));
}
mHearingAid.setVolume(gainDB);
@@ -675,7 +675,7 @@
case BluetoothProfile.HEADSET:
case BluetoothProfile.HEARING_AID:
case BluetoothProfile.LE_AUDIO:
- AudioService.sDeviceLogger.log(new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"BT profile service: connecting "
+ BluetoothProfile.getProfileName(profile) + " profile"));
mDeviceBroker.postBtProfileConnected(profile, proxy);
diff --git a/services/core/java/com/android/server/audio/FadeOutManager.java b/services/core/java/com/android/server/audio/FadeOutManager.java
index e54ee86..5f6f4b1 100644
--- a/services/core/java/com/android/server/audio/FadeOutManager.java
+++ b/services/core/java/com/android/server/audio/FadeOutManager.java
@@ -245,7 +245,7 @@
return;
}
try {
- PlaybackActivityMonitor.sEventLogger.log(
+ PlaybackActivityMonitor.sEventLogger.enqueue(
(new PlaybackActivityMonitor.FadeOutEvent(apc, skipRamp)).printLog(TAG));
apc.getPlayerProxy().applyVolumeShaper(
FADEOUT_VSHAPE,
@@ -262,7 +262,7 @@
final AudioPlaybackConfiguration apc = players.get(piid);
if (apc != null) {
try {
- PlaybackActivityMonitor.sEventLogger.log(
+ PlaybackActivityMonitor.sEventLogger.enqueue(
(new EventLogger.StringEvent("unfading out piid:"
+ piid)).printLog(TAG));
apc.getPlayerProxy().applyVolumeShaper(
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index 1ca27dd..27687b2 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -185,7 +185,7 @@
final FocusRequester focusOwner = stackIterator.next();
if (focusOwner.hasSameUid(uid) && focusOwner.hasSamePackage(packageName)) {
clientsToRemove.add(focusOwner.getClientId());
- mEventLogger.log((new EventLogger.StringEvent(
+ mEventLogger.enqueue((new EventLogger.StringEvent(
"focus owner:" + focusOwner.getClientId()
+ " in uid:" + uid + " pack: " + packageName
+ " getting AUDIOFOCUS_LOSS due to app suspension"))
@@ -433,7 +433,7 @@
FocusRequester fr = stackIterator.next();
if(fr.hasSameBinder(cb)) {
Log.i(TAG, "AudioFocus removeFocusStackEntryOnDeath(): removing entry for " + cb);
- mEventLogger.log(new EventLogger.StringEvent(
+ mEventLogger.enqueue(new EventLogger.StringEvent(
"focus requester:" + fr.getClientId()
+ " in uid:" + fr.getClientUid()
+ " pack:" + fr.getPackageName()
@@ -470,7 +470,7 @@
final FocusRequester fr = owner.getValue();
if (fr.hasSameBinder(cb)) {
ownerIterator.remove();
- mEventLogger.log(new EventLogger.StringEvent(
+ mEventLogger.enqueue(new EventLogger.StringEvent(
"focus requester:" + fr.getClientId()
+ " in uid:" + fr.getClientUid()
+ " pack:" + fr.getPackageName()
@@ -968,7 +968,7 @@
// supposed to be alone in bitfield
final int uid = (flags == AudioManager.AUDIOFOCUS_FLAG_TEST)
? testUid : Binder.getCallingUid();
- mEventLogger.log((new EventLogger.StringEvent(
+ mEventLogger.enqueue((new EventLogger.StringEvent(
"requestAudioFocus() from uid/pid " + uid
+ "/" + Binder.getCallingPid()
+ " AA=" + aa.usageToString() + "/" + aa.contentTypeToString()
@@ -1143,7 +1143,7 @@
.record();
// AudioAttributes are currently ignored, to be used for zones / a11y
- mEventLogger.log((new EventLogger.StringEvent(
+ mEventLogger.enqueue((new EventLogger.StringEvent(
"abandonAudioFocus() from uid/pid " + Binder.getCallingUid()
+ "/" + Binder.getCallingPid()
+ " clientId=" + clientId))
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 1af8c59..74bfa80 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -157,7 +157,7 @@
if (index >= 0) {
if (!disable) {
if (DEBUG) { // hidden behind DEBUG, too noisy otherwise
- sEventLogger.log(new EventLogger.StringEvent("unbanning uid:" + uid));
+ sEventLogger.enqueue(new EventLogger.StringEvent("unbanning uid:" + uid));
}
mBannedUids.remove(index);
// nothing else to do, future playback requests from this uid are ok
@@ -168,7 +168,7 @@
checkBanPlayer(apc, uid);
}
if (DEBUG) { // hidden behind DEBUG, too noisy otherwise
- sEventLogger.log(new EventLogger.StringEvent("banning uid:" + uid));
+ sEventLogger.enqueue(new EventLogger.StringEvent("banning uid:" + uid));
}
mBannedUids.add(new Integer(uid));
} // no else to handle, uid already not in list, so enabling again is no-op
@@ -209,7 +209,7 @@
updateAllowedCapturePolicy(apc, mAllowedCapturePolicies.get(uid));
}
}
- sEventLogger.log(new NewPlayerEvent(apc));
+ sEventLogger.enqueue(new NewPlayerEvent(apc));
synchronized(mPlayerLock) {
mPlayers.put(newPiid, apc);
maybeMutePlayerAwaitingConnection(apc);
@@ -229,7 +229,7 @@
synchronized(mPlayerLock) {
final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
if (checkConfigurationCaller(piid, apc, binderUid)) {
- sEventLogger.log(new AudioAttrEvent(piid, attr));
+ sEventLogger.enqueue(new AudioAttrEvent(piid, attr));
change = apc.handleAudioAttributesEvent(attr);
} else {
Log.e(TAG, "Error updating audio attributes");
@@ -322,7 +322,7 @@
return;
}
- sEventLogger.log(new PlayerEvent(piid, event, eventValue));
+ sEventLogger.enqueue(new PlayerEvent(piid, event, eventValue));
if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_PORT_ID) {
mEventHandler.sendMessage(
@@ -332,7 +332,7 @@
for (Integer uidInteger: mBannedUids) {
if (checkBanPlayer(apc, uidInteger.intValue())) {
// player was banned, do not update its state
- sEventLogger.log(new EventLogger.StringEvent(
+ sEventLogger.enqueue(new EventLogger.StringEvent(
"not starting piid:" + piid + " ,is banned"));
return;
}
@@ -412,7 +412,7 @@
public void playerHasOpPlayAudio(int piid, boolean hasOpPlayAudio, int binderUid) {
// no check on UID yet because this is only for logging at the moment
- sEventLogger.log(new PlayerOpPlayAudioEvent(piid, hasOpPlayAudio, binderUid));
+ sEventLogger.enqueue(new PlayerOpPlayAudioEvent(piid, hasOpPlayAudio, binderUid));
}
public void releasePlayer(int piid, int binderUid) {
@@ -421,7 +421,7 @@
synchronized(mPlayerLock) {
final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
if (checkConfigurationCaller(piid, apc, binderUid)) {
- sEventLogger.log(new EventLogger.StringEvent(
+ sEventLogger.enqueue(new EventLogger.StringEvent(
"releasing player piid:" + piid));
mPlayers.remove(new Integer(piid));
mDuckingManager.removeReleased(apc);
@@ -443,7 +443,7 @@
}
/*package*/ void onAudioServerDied() {
- sEventLogger.log(
+ sEventLogger.enqueue(
new EventLogger.StringEvent(
"clear port id to piid map"));
synchronized (mPlayerLock) {
@@ -768,7 +768,7 @@
}
if (mute) {
try {
- sEventLogger.log((new EventLogger.StringEvent("call: muting piid:"
+ sEventLogger.enqueue((new EventLogger.StringEvent("call: muting piid:"
+ piid + " uid:" + apc.getClientUid())).printLog(TAG));
apc.getPlayerProxy().setVolume(0.0f);
mMutedPlayers.add(new Integer(piid));
@@ -793,7 +793,7 @@
final AudioPlaybackConfiguration apc = mPlayers.get(piid);
if (apc != null) {
try {
- sEventLogger.log(new EventLogger.StringEvent("call: unmuting piid:"
+ sEventLogger.enqueue(new EventLogger.StringEvent("call: unmuting piid:"
+ piid).printLog(TAG));
apc.getPlayerProxy().setVolume(1.0f);
} catch (Exception e) {
@@ -1081,7 +1081,7 @@
return;
}
try {
- sEventLogger.log((new DuckEvent(apc, skipRamp)).printLog(TAG));
+ sEventLogger.enqueue((new DuckEvent(apc, skipRamp)).printLog(TAG));
apc.getPlayerProxy().applyVolumeShaper(
DUCK_VSHAPE,
skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
@@ -1096,7 +1096,7 @@
final AudioPlaybackConfiguration apc = players.get(piid);
if (apc != null) {
try {
- sEventLogger.log((new EventLogger.StringEvent("unducking piid:"
+ sEventLogger.enqueue((new EventLogger.StringEvent("unducking piid:"
+ piid)).printLog(TAG));
apc.getPlayerProxy().applyVolumeShaper(
DUCK_ID,
@@ -1310,8 +1310,9 @@
//==========================================================================================
void muteAwaitConnection(@NonNull int[] usagesToMute,
@NonNull AudioDeviceAttributes dev, long timeOutMs) {
- sEventLogger.loglogi(
- "muteAwaitConnection() dev:" + dev + " timeOutMs:" + timeOutMs, TAG);
+ sEventLogger.enqueueAndLog(
+ "muteAwaitConnection() dev:" + dev + " timeOutMs:" + timeOutMs,
+ EventLogger.Event.ALOGI, TAG);
synchronized (mPlayerLock) {
mutePlayersExpectingDevice(usagesToMute);
// schedule timeout (remove previously scheduled first)
@@ -1323,7 +1324,8 @@
}
void cancelMuteAwaitConnection(String source) {
- sEventLogger.loglogi("cancelMuteAwaitConnection() from:" + source, TAG);
+ sEventLogger.enqueueAndLog("cancelMuteAwaitConnection() from:" + source,
+ EventLogger.Event.ALOGI, TAG);
synchronized (mPlayerLock) {
// cancel scheduled timeout, ignore device, only one expected device at a time
mEventHandler.removeMessages(MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION);
@@ -1346,7 +1348,7 @@
@GuardedBy("mPlayerLock")
private void mutePlayersExpectingDevice(@NonNull int[] usagesToMute) {
- sEventLogger.log(new MuteAwaitConnectionEvent(usagesToMute));
+ sEventLogger.enqueue(new MuteAwaitConnectionEvent(usagesToMute));
mMutedUsagesAwaitingConnection = usagesToMute;
final Set<Integer> piidSet = mPlayers.keySet();
final Iterator<Integer> piidIterator = piidSet.iterator();
@@ -1369,7 +1371,7 @@
for (int usage : mMutedUsagesAwaitingConnection) {
if (usage == apc.getAudioAttributes().getUsage()) {
try {
- sEventLogger.log((new EventLogger.StringEvent(
+ sEventLogger.enqueue((new EventLogger.StringEvent(
"awaiting connection: muting piid:"
+ apc.getPlayerInterfaceId()
+ " uid:" + apc.getClientUid())).printLog(TAG));
@@ -1394,7 +1396,7 @@
continue;
}
try {
- sEventLogger.log(new EventLogger.StringEvent(
+ sEventLogger.enqueue(new EventLogger.StringEvent(
"unmuting piid:" + piid).printLog(TAG));
apc.getPlayerProxy().applyVolumeShaper(MUTE_AWAIT_CONNECTION_VSHAPE,
VolumeShaper.Operation.REVERSE);
@@ -1452,8 +1454,9 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION:
- sEventLogger.loglogi("Timeout for muting waiting for "
- + (AudioDeviceAttributes) msg.obj + ", unmuting", TAG);
+ sEventLogger.enqueueAndLog("Timeout for muting waiting for "
+ + (AudioDeviceAttributes) msg.obj + ", unmuting",
+ EventLogger.Event.ALOGI, TAG);
synchronized (mPlayerLock) {
unmutePlayersExpectingDevice();
}
@@ -1476,7 +1479,7 @@
synchronized (mPlayerLock) {
int piid = msg.arg1;
- sEventLogger.log(
+ sEventLogger.enqueue(
new PlayerEvent(piid, PLAYER_UPDATE_MUTED, eventValue));
final AudioPlaybackConfiguration apc = mPlayers.get(piid);
diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
index 2ba8882..652ea52 100644
--- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
@@ -164,7 +164,7 @@
}
if (MediaRecorder.isSystemOnlyAudioSource(source)) {
// still want to log event, it just won't appear in recording configurations;
- sEventLogger.log(new RecordingEvent(event, riid, config).printLog(TAG));
+ sEventLogger.enqueue(new RecordingEvent(event, riid, config).printLog(TAG));
return;
}
dispatchCallbacks(updateSnapshot(event, riid, config));
@@ -204,7 +204,7 @@
? AudioManager.RECORD_CONFIG_EVENT_STOP : AudioManager.RECORD_CONFIG_EVENT_NONE;
if (riid == AudioManager.RECORD_RIID_INVALID
|| configEvent == AudioManager.RECORD_CONFIG_EVENT_NONE) {
- sEventLogger.log(new RecordingEvent(event, riid, null).printLog(TAG));
+ sEventLogger.enqueue(new RecordingEvent(event, riid, null).printLog(TAG));
return;
}
dispatchCallbacks(updateSnapshot(configEvent, riid, null));
@@ -301,7 +301,7 @@
if (!state.hasDeathHandler()) {
if (state.isActiveConfiguration()) {
configChanged = true;
- sEventLogger.log(new RecordingEvent(
+ sEventLogger.enqueue(new RecordingEvent(
AudioManager.RECORD_CONFIG_EVENT_RELEASE,
state.getRiid(), state.getConfig()));
}
@@ -486,7 +486,7 @@
configChanged = false;
}
if (configChanged) {
- sEventLogger.log(new RecordingEvent(event, riid, state.getConfig()));
+ sEventLogger.enqueue(new RecordingEvent(event, riid, state.getConfig()));
configs = getActiveRecordingConfigurations(true /*isPrivileged*/);
}
}
diff --git a/services/core/java/com/android/server/audio/SoundEffectsHelper.java b/services/core/java/com/android/server/audio/SoundEffectsHelper.java
index 93eba50..79b54eb 100644
--- a/services/core/java/com/android/server/audio/SoundEffectsHelper.java
+++ b/services/core/java/com/android/server/audio/SoundEffectsHelper.java
@@ -164,7 +164,7 @@
}
private void logEvent(String msg) {
- mSfxLogger.log(new EventLogger.StringEvent(msg));
+ mSfxLogger.enqueue(new EventLogger.StringEvent(msg));
}
// All the methods below run on the worker thread
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 1563d33..2b525f1 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -1708,11 +1708,11 @@
private static void loglogi(String msg) {
- AudioService.sSpatialLogger.loglogi(msg, TAG);
+ AudioService.sSpatialLogger.enqueueAndLog(msg, EventLogger.Event.ALOGI, TAG);
}
private static String logloge(String msg) {
- AudioService.sSpatialLogger.loglog(msg, EventLogger.Event.ALOGE, TAG);
+ AudioService.sSpatialLogger.enqueueAndLog(msg, EventLogger.Event.ALOGE, TAG);
return msg;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
index aeb6b6e..969a174 100644
--- a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
+++ b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.hardware.biometrics.BiometricOverlayConstants;
import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
import android.os.RemoteException;
@@ -43,6 +44,7 @@
@NonNull private final Optional<IUdfpsOverlayController> mUdfpsOverlayController;
@NonNull private final Optional<ISidefpsController> mSidefpsController;
+ @NonNull private final Optional<IUdfpsOverlay> mUdfpsOverlay;
/**
* Create an overlay controller for each modality.
@@ -52,9 +54,11 @@
*/
public SensorOverlays(
@Nullable IUdfpsOverlayController udfpsOverlayController,
- @Nullable ISidefpsController sidefpsController) {
+ @Nullable ISidefpsController sidefpsController,
+ @Nullable IUdfpsOverlay udfpsOverlay) {
mUdfpsOverlayController = Optional.ofNullable(udfpsOverlayController);
mSidefpsController = Optional.ofNullable(sidefpsController);
+ mUdfpsOverlay = Optional.ofNullable(udfpsOverlay);
}
/**
@@ -90,6 +94,14 @@
Slog.e(TAG, "Remote exception when showing the UDFPS overlay", e);
}
}
+
+ if (mUdfpsOverlay.isPresent()) {
+ try {
+ mUdfpsOverlay.get().show(client.getRequestId(), sensorId, reason);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when showing the new UDFPS overlay", e);
+ }
+ }
}
/**
@@ -113,6 +125,14 @@
Slog.e(TAG, "Remote exception when hiding the UDFPS overlay", e);
}
}
+
+ if (mUdfpsOverlay.isPresent()) {
+ try {
+ mUdfpsOverlay.get().hide(sensorId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when hiding the new udfps overlay", e);
+ }
+ }
}
/**
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index b0dc28d..156e6bb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -52,6 +52,7 @@
import android.hardware.fingerprint.IFingerprintService;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Binder;
import android.os.Build;
@@ -874,6 +875,14 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
+ public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
+ provider.setUdfpsOverlay(controller);
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+ @Override
public void onPowerPressed() {
for (ServiceProvider provider : mRegistry.getProviders()) {
provider.onPowerPressed();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 0c29f56..05c2e29 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -26,6 +26,7 @@
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
@@ -129,6 +130,12 @@
void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller);
+ /**
+ * Sets udfps overlay
+ * @param controller udfps overlay
+ */
+ void setUdfpsOverlay(@NonNull IUdfpsOverlay controller);
+
void onPowerPressed();
/**
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 2e5663d..7f1fb1c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -33,6 +33,7 @@
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Build;
import android.os.Handler;
@@ -118,6 +119,7 @@
@NonNull LockoutCache lockoutCache,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@Nullable ISidefpsController sidefpsController,
+ @Nullable IUdfpsOverlay udfpsOverlay,
boolean allowBackgroundAuthentication,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull Handler handler,
@@ -145,7 +147,8 @@
false /* isKeyguardBypassEnabled */);
setRequestId(requestId);
mLockoutCache = lockoutCache;
- mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
+ mSensorOverlays = new SensorOverlays(udfpsOverlayController,
+ sidefpsController, udfpsOverlay);
mSensorProps = sensorProps;
mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */);
mHandler = handler;
@@ -248,8 +251,8 @@
if (authenticated && mSensorProps.isAnySidefpsType()) {
if (mHandler.hasMessages(MESSAGE_IGNORE_AUTH)) {
Slog.i(TAG, "(sideFPS) Ignoring auth due to recent power press");
- onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0,
- true);
+ onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED,
+ 0, true);
return;
}
delay = isKeyguard() ? mWaitForAuthKeyguard : mWaitForAuthBp;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index 0e89814..5282234 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.hardware.biometrics.BiometricOverlayConstants;
import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
import android.os.RemoteException;
@@ -54,12 +55,15 @@
@NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull String owner, int sensorId,
@NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
- @Nullable IUdfpsOverlayController udfpsOverlayController, boolean isStrongBiometric) {
+ @Nullable IUdfpsOverlayController udfpsOverlayController,
+ @Nullable IUdfpsOverlay udfpsOverlay,
+ boolean isStrongBiometric) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
true /* shouldVibrate */, biometricLogger, biometricContext);
setRequestId(requestId);
mIsStrongBiometric = isStrongBiometric;
- mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController*/);
+ mSensorOverlays = new SensorOverlays(udfpsOverlayController,
+ null /* sideFpsController*/, udfpsOverlay);
}
@Override
@@ -82,7 +86,8 @@
@Override
protected void startHalOperation() {
- mSensorOverlays.show(getSensorId(), BiometricOverlayConstants.REASON_AUTH_KEYGUARD, this);
+ mSensorOverlays.show(getSensorId(), BiometricOverlayConstants.REASON_AUTH_KEYGUARD,
+ this);
try {
mCancellationSignal = doDetectInteraction();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index 612d906..7e5d39f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -30,6 +30,7 @@
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.hardware.keymaster.HardwareAuthToken;
import android.os.IBinder;
@@ -86,6 +87,7 @@
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@Nullable ISidefpsController sidefpsController,
+ @Nullable IUdfpsOverlay udfpsOverlay,
int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) {
// UDFPS haptics occur when an image is acquired (instead of when the result is known)
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
@@ -93,7 +95,8 @@
biometricContext);
setRequestId(requestId);
mSensorProps = sensorProps;
- mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
+ mSensorOverlays = new SensorOverlays(udfpsOverlayController,
+ sidefpsController, udfpsOverlay);
mMaxTemplatesPerUser = maxTemplatesPerUser;
mALSProbeCallback = getLogger().getAmbientLightProbe(true /* startWithClient */);
@@ -162,7 +165,8 @@
@Override
protected void startHalOperation() {
- mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason), this);
+ mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason),
+ this);
BiometricNotificationUtils.cancelBadCalibrationNotification(getContext());
try {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 628c16a..a42ff9a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -40,6 +40,7 @@
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Binder;
import android.os.Handler;
@@ -108,6 +109,7 @@
@Nullable private IFingerprint mDaemon;
@Nullable private IUdfpsOverlayController mUdfpsOverlayController;
@Nullable private ISidefpsController mSidefpsController;
+ @Nullable private IUdfpsOverlay mUdfpsOverlay;
private final class BiometricTaskStackListener extends TaskStackListener {
@Override
@@ -383,7 +385,8 @@
BiometricsProtoEnums.CLIENT_UNKNOWN),
mBiometricContext,
mSensors.get(sensorId).getSensorProperties(),
- mUdfpsOverlayController, mSidefpsController, maxTemplatesPerUser, enrollReason);
+ mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
+ maxTemplatesPerUser, enrollReason);
scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
mBiometricStateCallback, new ClientMonitorCallback() {
@Override
@@ -418,7 +421,7 @@
opPackageName, sensorId,
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
mBiometricContext,
- mUdfpsOverlayController, isStrongBiometric);
+ mUdfpsOverlayController, mUdfpsOverlay, isStrongBiometric);
scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
@@ -439,10 +442,10 @@
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
mBiometricContext, isStrongBiometric,
mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
- mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication,
+ mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
+ allowBackgroundAuthentication,
mSensors.get(sensorId).getSensorProperties(), mHandler,
- Utils.getCurrentStrength(sensorId),
- SystemClock.elapsedRealtimeClock());
+ Utils.getCurrentStrength(sensorId), SystemClock.elapsedRealtimeClock());
scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
}
@@ -649,6 +652,11 @@
}
@Override
+ public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
+ mUdfpsOverlay = controller;
+ }
+
+ @Override
public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
boolean clearSchedulerBuffer) {
if (mSensors.contains(sensorId)) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 0e6df8e..dbc96df 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -38,6 +38,7 @@
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Handler;
import android.os.IBinder;
@@ -120,6 +121,7 @@
@NonNull private final HalResultController mHalResultController;
@Nullable private IUdfpsOverlayController mUdfpsOverlayController;
@Nullable private ISidefpsController mSidefpsController;
+ @Nullable private IUdfpsOverlay mUdfpsOverlay;
@NonNull private final BiometricContext mBiometricContext;
// for requests that do not use biometric prompt
@NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
@@ -594,7 +596,7 @@
createLogger(BiometricsProtoEnums.ACTION_ENROLL,
BiometricsProtoEnums.CLIENT_UNKNOWN),
mBiometricContext,
- mUdfpsOverlayController, mSidefpsController,
+ mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
enrollReason);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
@@ -640,7 +642,7 @@
mLazyDaemon, token, id, listener, userId, opPackageName,
mSensorProperties.sensorId,
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
- mBiometricContext, mUdfpsOverlayController,
+ mBiometricContext, mUdfpsOverlayController, mUdfpsOverlay,
isStrongBiometric);
mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
});
@@ -664,7 +666,7 @@
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
mBiometricContext, isStrongBiometric,
mTaskStackListener, mLockoutTracker,
- mUdfpsOverlayController, mSidefpsController,
+ mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
allowBackgroundAuthentication, mSensorProperties);
mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
});
@@ -853,6 +855,11 @@
}
@Override
+ public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
+ mUdfpsOverlay = controller;
+ }
+
+ @Override
public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
boolean clearSchedulerBuffer) {
final long sensorToken = proto.start(SensorServiceStateProto.SENSOR_STATES);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 0d620fd..56fa36e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -26,6 +26,7 @@
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
import android.os.RemoteException;
@@ -76,15 +77,18 @@
@NonNull LockoutFrameworkImpl lockoutTracker,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@Nullable ISidefpsController sidefpsController,
+ @Nullable IUdfpsOverlay udfpsOverlay,
boolean allowBackgroundAuthentication,
@NonNull FingerprintSensorPropertiesInternal sensorProps) {
super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
- isStrongBiometric, taskStackListener, lockoutTracker, allowBackgroundAuthentication,
- false /* shouldVibrate */, false /* isKeyguardBypassEnabled */);
+ isStrongBiometric, taskStackListener, lockoutTracker,
+ allowBackgroundAuthentication, false /* shouldVibrate */,
+ false /* isKeyguardBypassEnabled */);
setRequestId(requestId);
mLockoutFrameworkImpl = lockoutTracker;
- mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
+ mSensorOverlays = new SensorOverlays(udfpsOverlayController,
+ sidefpsController, udfpsOverlay);
mSensorProps = sensorProps;
mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index c2929d0..3e9b8ef 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -23,6 +23,7 @@
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricOverlayConstants;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
import android.os.RemoteException;
@@ -62,11 +63,13 @@
@NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
int sensorId,
@NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
- @Nullable IUdfpsOverlayController udfpsOverlayController, boolean isStrongBiometric) {
+ @Nullable IUdfpsOverlayController udfpsOverlayController,
+ @Nullable IUdfpsOverlay udfpsOverlay, boolean isStrongBiometric) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
true /* shouldVibrate */, biometricLogger, biometricContext);
setRequestId(requestId);
- mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController */);
+ mSensorOverlays = new SensorOverlays(udfpsOverlayController,
+ null /* sideFpsController */, udfpsOverlay);
mIsStrongBiometric = isStrongBiometric;
}
@@ -92,7 +95,8 @@
@Override
protected void startHalOperation() {
- mSensorOverlays.show(getSensorId(), BiometricOverlayConstants.REASON_AUTH_KEYGUARD, this);
+ mSensorOverlays.show(getSensorId(), BiometricOverlayConstants.REASON_AUTH_KEYGUARD,
+ this);
try {
getFreshDaemon().authenticate(0 /* operationId */, getTargetUserId());
@@ -128,8 +132,8 @@
}
@Override
- public void onAuthenticated(BiometricAuthenticator.Identifier identifier, boolean authenticated,
- ArrayList<Byte> hardwareAuthToken) {
+ public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
+ boolean authenticated, ArrayList<Byte> hardwareAuthToken) {
getLogger().logOnAuthenticated(getContext(), getOperationContext(),
authenticated, false /* requireConfirmation */,
getTargetUserId(), false /* isBiometricPrompt */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index 5d9af53..3371cec 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -26,6 +26,7 @@
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
import android.os.RemoteException;
@@ -67,12 +68,14 @@
@NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@Nullable ISidefpsController sidefpsController,
+ @Nullable IUdfpsOverlay udfpsOverlay,
@FingerprintManager.EnrollReason int enrollReason) {
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
timeoutSec, sensorId, true /* shouldVibrate */, biometricLogger,
biometricContext);
setRequestId(requestId);
- mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
+ mSensorOverlays = new SensorOverlays(udfpsOverlayController,
+ sidefpsController, udfpsOverlay);
mEnrollReason = enrollReason;
if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) {
@@ -102,7 +105,8 @@
@Override
protected void startHalOperation() {
- mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason), this);
+ mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason),
+ this);
BiometricNotificationUtils.cancelBadCalibrationNotification(getContext());
try {
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 6795b6b..45b0f0a6 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -79,6 +79,7 @@
import android.net.RouteInfo;
import android.net.UidRangeParcel;
import android.net.UnderlyingNetworkInfo;
+import android.net.Uri;
import android.net.VpnManager;
import android.net.VpnProfileState;
import android.net.VpnService;
@@ -226,6 +227,16 @@
private static final int VPN_DEFAULT_SCORE = 101;
/**
+ * The reset session timer for data stall. If a session has not successfully revalidated after
+ * the delay, the session will be torn down and restarted in an attempt to recover. Delay
+ * counter is reset on successful validation only.
+ *
+ * <p>If retries have exceeded the length of this array, the last entry in the array will be
+ * used as a repeating interval.
+ */
+ private static final long[] DATA_STALL_RESET_DELAYS_SEC = {30L, 60L, 120L, 240L, 480L, 960L};
+
+ /**
* The initial token value of IKE session.
*/
private static final int STARTING_TOKEN = -1;
@@ -271,6 +282,7 @@
private final UserManager mUserManager;
private final VpnProfileStore mVpnProfileStore;
+ protected boolean mDataStallSuspected = false;
@VisibleForTesting
VpnProfileStore getVpnProfileStore() {
@@ -522,10 +534,28 @@
@NonNull LinkProperties lp,
@NonNull NetworkScore score,
@NonNull NetworkAgentConfig config,
- @Nullable NetworkProvider provider) {
+ @Nullable NetworkProvider provider,
+ @Nullable ValidationStatusCallback callback) {
return new VpnNetworkAgentWrapper(
- context, looper, logTag, nc, lp, score, config, provider);
+ context, looper, logTag, nc, lp, score, config, provider, callback);
}
+
+ /**
+ * Get the length of time to wait before resetting the ike session when a data stall is
+ * suspected.
+ */
+ public long getDataStallResetSessionSeconds(int count) {
+ if (count >= DATA_STALL_RESET_DELAYS_SEC.length) {
+ return DATA_STALL_RESET_DELAYS_SEC[DATA_STALL_RESET_DELAYS_SEC.length - 1];
+ } else {
+ return DATA_STALL_RESET_DELAYS_SEC[count];
+ }
+ }
+ }
+
+ @VisibleForTesting
+ interface ValidationStatusCallback {
+ void onValidationStatus(int status);
}
public Vpn(Looper looper, Context context, INetworkManagementService netService, INetd netd,
@@ -1460,6 +1490,11 @@
@GuardedBy("this")
private void agentConnect() {
+ agentConnect(null /* validationCallback */);
+ }
+
+ @GuardedBy("this")
+ private void agentConnect(@Nullable ValidationStatusCallback validationCallback) {
LinkProperties lp = makeLinkProperties();
// VPN either provide a default route (IPv4 or IPv6 or both), or they are a split tunnel
@@ -1507,7 +1542,7 @@
mNetworkAgent = mDeps.newNetworkAgent(mContext, mLooper, NETWORKTYPE /* logtag */,
mNetworkCapabilities, lp,
new NetworkScore.Builder().setLegacyInt(VPN_DEFAULT_SCORE).build(),
- networkAgentConfig, mNetworkProvider);
+ networkAgentConfig, mNetworkProvider, validationCallback);
final long token = Binder.clearCallingIdentity();
try {
mNetworkAgent.register();
@@ -2723,7 +2758,7 @@
@Nullable private ScheduledFuture<?> mScheduledHandleNetworkLostFuture;
@Nullable private ScheduledFuture<?> mScheduledHandleRetryIkeSessionFuture;
-
+ @Nullable private ScheduledFuture<?> mScheduledHandleDataStallFuture;
/** Signal to ensure shutdown is honored even if a new Network is connected. */
private boolean mIsRunning = true;
@@ -2750,6 +2785,14 @@
private boolean mMobikeEnabled = false;
/**
+ * The number of attempts to reset the IKE session since the last successful connection.
+ *
+ * <p>This variable controls the retry delay, and is reset when the VPN pass network
+ * validation.
+ */
+ private int mDataStallRetryCount = 0;
+
+ /**
* The number of attempts since the last successful connection.
*
* <p>This variable controls the retry delay, and is reset when a new IKE session is
@@ -2931,7 +2974,7 @@
if (isSettingsVpnLocked()) {
prepareStatusIntent();
}
- agentConnect();
+ agentConnect(this::onValidationStatus);
return; // Link properties are already sent.
} else {
// Underlying networks also set in agentConnect()
@@ -3200,18 +3243,52 @@
// Ignore stale runner.
if (mVpnRunner != Vpn.IkeV2VpnRunner.this) return;
- // Handle the report only for current VPN network.
+ // Handle the report only for current VPN network. If data stall is already
+ // reported, ignoring the other reports. It means that the stall is not
+ // recovered by MOBIKE and should be on the way to reset the ike session.
if (mNetworkAgent != null
- && mNetworkAgent.getNetwork().equals(report.getNetwork())) {
+ && mNetworkAgent.getNetwork().equals(report.getNetwork())
+ && !mDataStallSuspected) {
Log.d(TAG, "Data stall suspected");
// Trigger MOBIKE.
maybeMigrateIkeSession(mActiveNetwork);
+ mDataStallSuspected = true;
}
}
}
}
+ public void onValidationStatus(int status) {
+ if (status == NetworkAgent.VALIDATION_STATUS_VALID) {
+ // No data stall now. Reset it.
+ mExecutor.execute(() -> {
+ mDataStallSuspected = false;
+ mDataStallRetryCount = 0;
+ if (mScheduledHandleDataStallFuture != null) {
+ Log.d(TAG, "Recovered from stall. Cancel pending reset action.");
+ mScheduledHandleDataStallFuture.cancel(false /* mayInterruptIfRunning */);
+ mScheduledHandleDataStallFuture = null;
+ }
+ });
+ } else {
+ // Skip other invalid status if the scheduled recovery exists.
+ if (mScheduledHandleDataStallFuture != null) return;
+
+ mScheduledHandleDataStallFuture = mExecutor.schedule(() -> {
+ if (mDataStallSuspected) {
+ Log.d(TAG, "Reset session to recover stalled network");
+ // This will reset old state if it exists.
+ startIkeSession(mActiveNetwork);
+ }
+
+ // Reset mScheduledHandleDataStallFuture since it's already run on executor
+ // thread.
+ mScheduledHandleDataStallFuture = null;
+ }, mDeps.getDataStallResetSessionSeconds(mDataStallRetryCount++), TimeUnit.SECONDS);
+ }
+ }
+
/**
* Handles loss of the default underlying network
*
@@ -4339,6 +4416,7 @@
// un-finalized.
@VisibleForTesting
public static class VpnNetworkAgentWrapper extends NetworkAgent {
+ private final ValidationStatusCallback mCallback;
/** Create an VpnNetworkAgentWrapper */
public VpnNetworkAgentWrapper(
@NonNull Context context,
@@ -4348,8 +4426,10 @@
@NonNull LinkProperties lp,
@NonNull NetworkScore score,
@NonNull NetworkAgentConfig config,
- @Nullable NetworkProvider provider) {
+ @Nullable NetworkProvider provider,
+ @Nullable ValidationStatusCallback callback) {
super(context, looper, logTag, nc, lp, score, config, provider);
+ mCallback = callback;
}
/** Update the LinkProperties */
@@ -4371,6 +4451,13 @@
public void onNetworkUnwanted() {
// We are user controlled, not driven by NetworkRequest.
}
+
+ @Override
+ public void onValidationStatus(int status, Uri redirectUri) {
+ if (mCallback != null) {
+ mCallback.onValidationStatus(status);
+ }
+ }
}
/**
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 7e80b7d..e907ebf 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -127,6 +127,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.AnimationThread;
import com.android.server.DisplayThread;
@@ -151,6 +152,7 @@
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
+
/**
* Manages attached displays.
* <p>
@@ -1900,6 +1902,14 @@
if (displayDevice == null) {
return;
}
+ if (mLogicalDisplayMapper.getDisplayLocked(displayDevice)
+ .getDisplayInfoLocked().type == Display.TYPE_INTERNAL) {
+ FrameworkStatsLog.write(FrameworkStatsLog.BRIGHTNESS_CONFIGURATION_UPDATED,
+ c.getCurve().first,
+ c.getCurve().second,
+ // should not be logged for virtual displays
+ uniqueId);
+ }
mPersistentDataStore.setBrightnessConfigurationForDisplayLocked(c, displayDevice,
userSerial, packageName);
} finally {
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index cd9ef09..c3313e0 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -245,6 +245,7 @@
if (mSentStartBroadcast) {
mContext.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL);
+ mSentStartBroadcast = false;
}
mActivityTaskManager.removeRootTasksWithActivityTypes(
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 6e2cceb..4ca4817 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -23,12 +23,14 @@
import static com.android.server.wm.ActivityInterceptorCallback.DREAM_MANAGER_ORDERED_ID;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -38,6 +40,8 @@
import android.content.pm.ServiceInfo;
import android.database.ContentObserver;
import android.hardware.display.AmbientDisplayConfiguration;
+import android.net.Uri;
+import android.os.BatteryManager;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
@@ -72,6 +76,8 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -88,6 +94,15 @@
private static final String DOZE_WAKE_LOCK_TAG = "dream:doze";
private static final String DREAM_WAKE_LOCK_TAG = "dream:dream";
+ /** Constants for the when to activate dreams. */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({DREAM_ON_DOCK, DREAM_ON_CHARGE, DREAM_ON_DOCK_OR_CHARGE})
+ public @interface WhenToDream {}
+ private static final int DREAM_DISABLED = 0x0;
+ private static final int DREAM_ON_DOCK = 0x1;
+ private static final int DREAM_ON_CHARGE = 0x2;
+ private static final int DREAM_ON_DOCK_OR_CHARGE = 0x3;
+
private final Object mLock = new Object();
private final Context mContext;
@@ -101,12 +116,20 @@
private final DreamUiEventLogger mDreamUiEventLogger;
private final ComponentName mAmbientDisplayComponent;
private final boolean mDismissDreamOnActivityStart;
+ private final boolean mDreamsOnlyEnabledForSystemUser;
+ private final boolean mDreamsEnabledByDefaultConfig;
+ private final boolean mDreamsActivatedOnChargeByDefault;
+ private final boolean mDreamsActivatedOnDockByDefault;
@GuardedBy("mLock")
private DreamRecord mCurrentDream;
private boolean mForceAmbientDisplayEnabled;
- private final boolean mDreamsOnlyEnabledForSystemUser;
+ private SettingsObserver mSettingsObserver;
+ private boolean mDreamsEnabledSetting;
+ @WhenToDream private int mWhenToDream;
+ private boolean mIsDocked;
+ private boolean mIsCharging;
// A temporary dream component that, when present, takes precedence over user configured dream
// component.
@@ -144,6 +167,37 @@
}
};
+ private final BroadcastReceiver mChargingReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mIsCharging = (BatteryManager.ACTION_CHARGING.equals(intent.getAction()));
+ }
+ };
+
+ private final BroadcastReceiver mDockStateReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_DOCK_EVENT.equals(intent.getAction())) {
+ int dockState = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
+ Intent.EXTRA_DOCK_STATE_UNDOCKED);
+ mIsDocked = dockState != Intent.EXTRA_DOCK_STATE_UNDOCKED;
+ }
+ }
+ };
+
+ private final class SettingsObserver extends ContentObserver {
+ SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ synchronized (mLock) {
+ updateWhenToDreamSettings();
+ }
+ }
+ }
+
public DreamManagerService(Context context) {
super(context);
mContext = context;
@@ -164,6 +218,14 @@
mContext.getResources().getBoolean(R.bool.config_dreamsOnlyEnabledForSystemUser);
mDismissDreamOnActivityStart = mContext.getResources().getBoolean(
R.bool.config_dismissDreamOnActivityStart);
+
+ mDreamsEnabledByDefaultConfig = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_dreamsEnabledByDefault);
+ mDreamsActivatedOnChargeByDefault = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault);
+ mDreamsActivatedOnDockByDefault = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault);
+ mSettingsObserver = new SettingsObserver(mHandler);
}
@Override
@@ -197,6 +259,30 @@
DREAM_MANAGER_ORDERED_ID,
mActivityInterceptorCallback);
}
+
+ mContext.registerReceiver(
+ mDockStateReceiver, new IntentFilter(Intent.ACTION_DOCK_EVENT));
+ IntentFilter chargingIntentFilter = new IntentFilter();
+ chargingIntentFilter.addAction(BatteryManager.ACTION_CHARGING);
+ chargingIntentFilter.addAction(BatteryManager.ACTION_DISCHARGING);
+ mContext.registerReceiver(mChargingReceiver, chargingIntentFilter);
+
+ mSettingsObserver = new SettingsObserver(mHandler);
+ mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP),
+ false, mSettingsObserver, UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK),
+ false, mSettingsObserver, UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.SCREENSAVER_ENABLED),
+ false, mSettingsObserver, UserHandle.USER_ALL);
+
+ // We don't get an initial broadcast for the batter state, so we have to initialize
+ // directly from BatteryManager.
+ mIsCharging = mContext.getSystemService(BatteryManager.class).isCharging();
+
+ updateWhenToDreamSettings();
}
}
@@ -207,6 +293,14 @@
pw.println("mCurrentDream=" + mCurrentDream);
pw.println("mForceAmbientDisplayEnabled=" + mForceAmbientDisplayEnabled);
pw.println("mDreamsOnlyEnabledForSystemUser=" + mDreamsOnlyEnabledForSystemUser);
+ pw.println("mDreamsEnabledSetting=" + mDreamsEnabledSetting);
+ pw.println("mForceAmbientDisplayEnabled=" + mForceAmbientDisplayEnabled);
+ pw.println("mDreamsOnlyEnabledForSystemUser=" + mDreamsOnlyEnabledForSystemUser);
+ pw.println("mDreamsActivatedOnDockByDefault=" + mDreamsActivatedOnDockByDefault);
+ pw.println("mDreamsActivatedOnChargeByDefault=" + mDreamsActivatedOnChargeByDefault);
+ pw.println("mIsDocked=" + mIsDocked);
+ pw.println("mIsCharging=" + mIsCharging);
+ pw.println("mWhenToDream=" + mWhenToDream);
pw.println("getDozeComponent()=" + getDozeComponent());
pw.println();
@@ -214,7 +308,28 @@
}
}
- /** Whether a real dream is occurring. */
+ private void updateWhenToDreamSettings() {
+ synchronized (mLock) {
+ final ContentResolver resolver = mContext.getContentResolver();
+
+ final int activateWhenCharging = (Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+ mDreamsActivatedOnChargeByDefault ? 1 : 0,
+ UserHandle.USER_CURRENT) != 0) ? DREAM_ON_CHARGE : DREAM_DISABLED;
+ final int activateWhenDocked = (Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
+ mDreamsActivatedOnDockByDefault ? 1 : 0,
+ UserHandle.USER_CURRENT) != 0) ? DREAM_ON_DOCK : DREAM_DISABLED;
+ mWhenToDream = activateWhenCharging + activateWhenDocked;
+
+ mDreamsEnabledSetting = (Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.SCREENSAVER_ENABLED,
+ mDreamsEnabledByDefaultConfig ? 1 : 0,
+ UserHandle.USER_CURRENT) != 0);
+ }
+ }
+
+ /** Whether a real dream is occurring. */
private boolean isDreamingInternal() {
synchronized (mLock) {
return mCurrentDream != null && !mCurrentDream.isPreview
@@ -236,6 +351,30 @@
}
}
+ /** Whether dreaming can start given user settings and the current dock/charge state. */
+ private boolean canStartDreamingInternal(boolean isScreenOn) {
+ synchronized (mLock) {
+ // Can't start dreaming if we are already dreaming.
+ if (isScreenOn && isDreamingInternal()) {
+ return false;
+ }
+
+ if (!mDreamsEnabledSetting) {
+ return false;
+ }
+
+ if ((mWhenToDream & DREAM_ON_CHARGE) == DREAM_ON_CHARGE) {
+ return mIsCharging;
+ }
+
+ if ((mWhenToDream & DREAM_ON_DOCK) == DREAM_ON_DOCK) {
+ return mIsDocked;
+ }
+
+ return false;
+ }
+ }
+
protected void requestStartDreamFromShell() {
requestDreamInternal();
}
@@ -869,6 +1008,11 @@
}
@Override
+ public boolean canStartDreaming(boolean isScreenOn) {
+ return canStartDreamingInternal(isScreenOn);
+ }
+
+ @Override
public ComponentName getActiveDreamComponent(boolean doze) {
return getActiveDreamComponentInternal(doze);
}
diff --git a/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java b/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java
index 5253d34..d4e8f27 100644
--- a/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java
+++ b/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java
@@ -19,28 +19,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringRes;
-import android.annotation.UserIdInt;
-import android.app.AppGlobals;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.TimeUtils;
-
-import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
/**
* Gets the service name using a framework resources, temporarily changing the service if necessary
@@ -48,259 +29,42 @@
*
* @hide
*/
-public final class FrameworkResourcesServiceNameResolver implements ServiceNameResolver {
+public final class FrameworkResourcesServiceNameResolver extends ServiceNameBaseResolver {
- private static final String TAG = FrameworkResourcesServiceNameResolver.class.getSimpleName();
-
- /** Handler message to {@link #resetTemporaryService(int)} */
- private static final int MSG_RESET_TEMPORARY_SERVICE = 0;
-
- @NonNull
- private final Context mContext;
- @NonNull
- private final Object mLock = new Object();
- @StringRes
private final int mStringResourceId;
@ArrayRes
private final int mArrayResourceId;
- private final boolean mIsMultiple;
- /**
- * Map of temporary service name list set by {@link #setTemporaryServices(int, String[], int)},
- * keyed by {@code userId}.
- *
- * <p>Typically used by Shell command and/or CTS tests to configure temporary services if
- * mIsMultiple is true.
- */
- @GuardedBy("mLock")
- private final SparseArray<String[]> mTemporaryServiceNamesList = new SparseArray<>();
- /**
- * Map of default services that have been disabled by
- * {@link #setDefaultServiceEnabled(int, boolean)},keyed by {@code userId}.
- *
- * <p>Typically used by Shell command and/or CTS tests.
- */
- @GuardedBy("mLock")
- private final SparseBooleanArray mDefaultServicesDisabled = new SparseBooleanArray();
- @Nullable
- private NameResolverListener mOnSetCallback;
- /**
- * When the temporary service will expire (and reset back to the default).
- */
- @GuardedBy("mLock")
- private long mTemporaryServiceExpiration;
-
- /**
- * Handler used to reset the temporary service name.
- */
- @GuardedBy("mLock")
- private Handler mTemporaryHandler;
public FrameworkResourcesServiceNameResolver(@NonNull Context context,
@StringRes int resourceId) {
- mContext = context;
+ super(context, false);
mStringResourceId = resourceId;
mArrayResourceId = -1;
- mIsMultiple = false;
}
public FrameworkResourcesServiceNameResolver(@NonNull Context context,
@ArrayRes int resourceId, boolean isMultiple) {
+ super(context, isMultiple);
if (!isMultiple) {
throw new UnsupportedOperationException("Please use "
+ "FrameworkResourcesServiceNameResolver(context, @StringRes int) constructor "
+ "if single service mode is requested.");
}
- mContext = context;
mStringResourceId = -1;
mArrayResourceId = resourceId;
- mIsMultiple = true;
}
@Override
- public void setOnTemporaryServiceNameChangedCallback(@NonNull NameResolverListener callback) {
- synchronized (mLock) {
- this.mOnSetCallback = callback;
- }
+ public String[] readServiceNameList(int userId) {
+ return mContext.getResources().getStringArray(mArrayResourceId);
}
+ @Nullable
@Override
- public String getServiceName(@UserIdInt int userId) {
- String[] serviceNames = getServiceNameList(userId);
- return (serviceNames == null || serviceNames.length == 0) ? null : serviceNames[0];
+ public String readServiceName(int userId) {
+ return mContext.getResources().getString(mStringResourceId);
}
- @Override
- public String getDefaultServiceName(@UserIdInt int userId) {
- String[] serviceNames = getDefaultServiceNameList(userId);
- return (serviceNames == null || serviceNames.length == 0) ? null : serviceNames[0];
- }
-
- /**
- * Gets the default list of the service names for the given user.
- *
- * <p>Typically implemented by services which want to provide multiple backends.
- */
- @Override
- public String[] getServiceNameList(int userId) {
- synchronized (mLock) {
- String[] temporaryNames = mTemporaryServiceNamesList.get(userId);
- if (temporaryNames != null) {
- // Always log it, as it should only be used on CTS or during development
- Slog.w(TAG, "getServiceName(): using temporary name "
- + Arrays.toString(temporaryNames) + " for user " + userId);
- return temporaryNames;
- }
- final boolean disabled = mDefaultServicesDisabled.get(userId);
- if (disabled) {
- // Always log it, as it should only be used on CTS or during development
- Slog.w(TAG, "getServiceName(): temporary name not set and default disabled for "
- + "user " + userId);
- return null;
- }
- return getDefaultServiceNameList(userId);
-
- }
- }
-
- /**
- * Gets the default list of the service names for the given user.
- *
- * <p>Typically implemented by services which want to provide multiple backends.
- */
- @Override
- public String[] getDefaultServiceNameList(int userId) {
- synchronized (mLock) {
- if (mIsMultiple) {
- String[] serviceNameList = mContext.getResources().getStringArray(mArrayResourceId);
- // Filter out unimplemented services
- // Initialize the validated array as null because we do not know the final size.
- List<String> validatedServiceNameList = new ArrayList<>();
- try {
- for (int i = 0; i < serviceNameList.length; i++) {
- if (TextUtils.isEmpty(serviceNameList[i])) {
- continue;
- }
- ComponentName serviceComponent = ComponentName.unflattenFromString(
- serviceNameList[i]);
- ServiceInfo serviceInfo = AppGlobals.getPackageManager().getServiceInfo(
- serviceComponent,
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
- if (serviceInfo != null) {
- validatedServiceNameList.add(serviceNameList[i]);
- }
- }
- } catch (Exception e) {
- Slog.e(TAG, "Could not validate provided services.", e);
- }
- String[] validatedServiceNameArray = new String[validatedServiceNameList.size()];
- return validatedServiceNameList.toArray(validatedServiceNameArray);
- } else {
- final String name = mContext.getString(mStringResourceId);
- return TextUtils.isEmpty(name) ? new String[0] : new String[]{name};
- }
- }
- }
-
- @Override
- public boolean isConfiguredInMultipleMode() {
- return mIsMultiple;
- }
-
- @Override
- public boolean isTemporary(@UserIdInt int userId) {
- synchronized (mLock) {
- return mTemporaryServiceNamesList.get(userId) != null;
- }
- }
-
- @Override
- public void setTemporaryService(@UserIdInt int userId, @NonNull String componentName,
- int durationMs) {
- setTemporaryServices(userId, new String[]{componentName}, durationMs);
- }
-
- @Override
- public void setTemporaryServices(int userId, @NonNull String[] componentNames, int durationMs) {
- synchronized (mLock) {
- mTemporaryServiceNamesList.put(userId, componentNames);
-
- if (mTemporaryHandler == null) {
- mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == MSG_RESET_TEMPORARY_SERVICE) {
- synchronized (mLock) {
- resetTemporaryService(userId);
- }
- } else {
- Slog.wtf(TAG, "invalid handler msg: " + msg);
- }
- }
- };
- } else {
- mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
- }
- mTemporaryServiceExpiration = SystemClock.elapsedRealtime() + durationMs;
- mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, durationMs);
- for (int i = 0; i < componentNames.length; i++) {
- notifyTemporaryServiceNameChangedLocked(userId, componentNames[i],
- /* isTemporary= */ true);
- }
- }
- }
-
- @Override
- public void resetTemporaryService(@UserIdInt int userId) {
- synchronized (mLock) {
- Slog.i(TAG, "resetting temporary service for user " + userId + " from "
- + Arrays.toString(mTemporaryServiceNamesList.get(userId)));
- mTemporaryServiceNamesList.remove(userId);
- if (mTemporaryHandler != null) {
- mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
- mTemporaryHandler = null;
- }
- notifyTemporaryServiceNameChangedLocked(userId, /* newTemporaryName= */ null,
- /* isTemporary= */ false);
- }
- }
-
- @Override
- public boolean setDefaultServiceEnabled(int userId, boolean enabled) {
- synchronized (mLock) {
- final boolean currentlyEnabled = isDefaultServiceEnabledLocked(userId);
- if (currentlyEnabled == enabled) {
- Slog.i(TAG, "setDefaultServiceEnabled(" + userId + "): already " + enabled);
- return false;
- }
- if (enabled) {
- Slog.i(TAG, "disabling default service for user " + userId);
- mDefaultServicesDisabled.removeAt(userId);
- } else {
- Slog.i(TAG, "enabling default service for user " + userId);
- mDefaultServicesDisabled.put(userId, true);
- }
- }
- return true;
- }
-
- @Override
- public boolean isDefaultServiceEnabled(int userId) {
- synchronized (mLock) {
- return isDefaultServiceEnabledLocked(userId);
- }
- }
-
- private boolean isDefaultServiceEnabledLocked(int userId) {
- return !mDefaultServicesDisabled.get(userId);
- }
-
- @Override
- public String toString() {
- synchronized (mLock) {
- return "FrameworkResourcesServiceNamer[temps=" + mTemporaryServiceNamesList + "]";
- }
- }
// TODO(b/117779333): support proto
@Override
@@ -314,31 +78,4 @@
pw.print(mDefaultServicesDisabled.size());
}
}
-
- // TODO(b/117779333): support proto
- @Override
- public void dumpShort(@NonNull PrintWriter pw, @UserIdInt int userId) {
- synchronized (mLock) {
- final String[] temporaryNames = mTemporaryServiceNamesList.get(userId);
- if (temporaryNames != null) {
- pw.print("tmpName=");
- pw.print(Arrays.toString(temporaryNames));
- final long ttl = mTemporaryServiceExpiration - SystemClock.elapsedRealtime();
- pw.print(" (expires in ");
- TimeUtils.formatDuration(ttl, pw);
- pw.print("), ");
- }
- pw.print("defaultName=");
- pw.print(getDefaultServiceName(userId));
- final boolean disabled = mDefaultServicesDisabled.get(userId);
- pw.println(disabled ? " (disabled)" : " (enabled)");
- }
- }
-
- private void notifyTemporaryServiceNameChangedLocked(@UserIdInt int userId,
- @Nullable String newTemporaryName, boolean isTemporary) {
- if (mOnSetCallback != null) {
- mOnSetCallback.onNameResolved(userId, newTemporaryName, isTemporary);
- }
- }
}
diff --git a/services/core/java/com/android/server/infra/SecureSettingsServiceNameResolver.java b/services/core/java/com/android/server/infra/SecureSettingsServiceNameResolver.java
index cac7f53..17d75e6 100644
--- a/services/core/java/com/android/server/infra/SecureSettingsServiceNameResolver.java
+++ b/services/core/java/com/android/server/infra/SecureSettingsServiceNameResolver.java
@@ -19,8 +19,11 @@
import android.annotation.UserIdInt;
import android.content.Context;
import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArraySet;
import java.io.PrintWriter;
+import java.util.Set;
/**
* Gets the service name using a property from the {@link android.provider.Settings.Secure}
@@ -28,21 +31,34 @@
*
* @hide
*/
-public final class SecureSettingsServiceNameResolver implements ServiceNameResolver {
+public final class SecureSettingsServiceNameResolver extends ServiceNameBaseResolver {
+ /**
+ * The delimiter to be used to parse the secure settings string. Services must make sure
+ * that this delimiter is used while adding component names to their secure setting property.
+ */
+ private static final char COMPONENT_NAME_SEPARATOR = ':';
- private final @NonNull Context mContext;
+ private final TextUtils.SimpleStringSplitter mStringColonSplitter =
+ new TextUtils.SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
@NonNull
private final String mProperty;
public SecureSettingsServiceNameResolver(@NonNull Context context, @NonNull String property) {
- mContext = context;
- mProperty = property;
+ this(context, property, /*isMultiple*/false);
}
- @Override
- public String getDefaultServiceName(@UserIdInt int userId) {
- return Settings.Secure.getStringForUser(mContext.getContentResolver(), mProperty, userId);
+ /**
+ *
+ * @param context the context required to retrieve the secure setting value
+ * @param property name of the secure setting key
+ * @param isMultiple true if the system service using this resolver needs to connect to
+ * multiple remote services, false otherwise
+ */
+ public SecureSettingsServiceNameResolver(@NonNull Context context, @NonNull String property,
+ boolean isMultiple) {
+ super(context, isMultiple);
+ mProperty = property;
}
// TODO(b/117779333): support proto
@@ -61,4 +77,34 @@
public String toString() {
return "SecureSettingsServiceNameResolver[" + mProperty + "]";
}
+
+ @Override
+ public String[] readServiceNameList(int userId) {
+ return parseColonDelimitedServiceNames(
+ Settings.Secure.getStringForUser(
+ mContext.getContentResolver(), mProperty, userId));
+ }
+
+ @Override
+ public String readServiceName(int userId) {
+ return Settings.Secure.getStringForUser(
+ mContext.getContentResolver(), mProperty, userId);
+ }
+
+ private String[] parseColonDelimitedServiceNames(String serviceNames) {
+ final Set<String> delimitedServices = new ArraySet<>();
+ if (!TextUtils.isEmpty(serviceNames)) {
+ final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
+ splitter.setString(serviceNames);
+ while (splitter.hasNext()) {
+ final String str = splitter.next();
+ if (TextUtils.isEmpty(str)) {
+ continue;
+ }
+ delimitedServices.add(str);
+ }
+ }
+ String[] delimitedServicesArray = new String[delimitedServices.size()];
+ return delimitedServices.toArray(delimitedServicesArray);
+ }
}
diff --git a/services/core/java/com/android/server/infra/ServiceNameBaseResolver.java b/services/core/java/com/android/server/infra/ServiceNameBaseResolver.java
new file mode 100644
index 0000000..76ea05e
--- /dev/null
+++ b/services/core/java/com/android/server/infra/ServiceNameBaseResolver.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.infra;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Gets the service name using a framework resources, temporarily changing the service if necessary
+ * (typically during CTS tests or service development).
+ *
+ * @hide
+ */
+public abstract class ServiceNameBaseResolver implements ServiceNameResolver {
+
+ private static final String TAG = ServiceNameBaseResolver.class.getSimpleName();
+
+ /** Handler message to {@link #resetTemporaryService(int)} */
+ private static final int MSG_RESET_TEMPORARY_SERVICE = 0;
+
+ @NonNull
+ protected final Context mContext;
+ @NonNull
+ protected final Object mLock = new Object();
+
+ protected final boolean mIsMultiple;
+ /**
+ * Map of temporary service name list set by {@link #setTemporaryServices(int, String[], int)},
+ * keyed by {@code userId}.
+ *
+ * <p>Typically used by Shell command and/or CTS tests to configure temporary services if
+ * mIsMultiple is true.
+ */
+ @GuardedBy("mLock")
+ protected final SparseArray<String[]> mTemporaryServiceNamesList = new SparseArray<>();
+ /**
+ * Map of default services that have been disabled by
+ * {@link #setDefaultServiceEnabled(int, boolean)},keyed by {@code userId}.
+ *
+ * <p>Typically used by Shell command and/or CTS tests.
+ */
+ @GuardedBy("mLock")
+ protected final SparseBooleanArray mDefaultServicesDisabled = new SparseBooleanArray();
+ @Nullable
+ private NameResolverListener mOnSetCallback;
+ /**
+ * When the temporary service will expire (and reset back to the default).
+ */
+ @GuardedBy("mLock")
+ private long mTemporaryServiceExpiration;
+
+ /**
+ * Handler used to reset the temporary service name.
+ */
+ @GuardedBy("mLock")
+ private Handler mTemporaryHandler;
+
+ protected ServiceNameBaseResolver(Context context, boolean isMultiple) {
+ mContext = context;
+ mIsMultiple = isMultiple;
+ }
+
+ @Override
+ public void setOnTemporaryServiceNameChangedCallback(@NonNull NameResolverListener callback) {
+ synchronized (mLock) {
+ this.mOnSetCallback = callback;
+ }
+ }
+
+ @Override
+ public String getServiceName(@UserIdInt int userId) {
+ String[] serviceNames = getServiceNameList(userId);
+ return (serviceNames == null || serviceNames.length == 0) ? null : serviceNames[0];
+ }
+
+ @Override
+ public String getDefaultServiceName(@UserIdInt int userId) {
+ String[] serviceNames = getDefaultServiceNameList(userId);
+ return (serviceNames == null || serviceNames.length == 0) ? null : serviceNames[0];
+ }
+
+ /**
+ * Gets the default list of the service names for the given user.
+ *
+ * <p>Typically implemented by services which want to provide multiple backends.
+ */
+ @Override
+ public String[] getServiceNameList(int userId) {
+ synchronized (mLock) {
+ String[] temporaryNames = mTemporaryServiceNamesList.get(userId);
+ if (temporaryNames != null) {
+ // Always log it, as it should only be used on CTS or during development
+ Slog.w(TAG, "getServiceName(): using temporary name "
+ + Arrays.toString(temporaryNames) + " for user " + userId);
+ return temporaryNames;
+ }
+ final boolean disabled = mDefaultServicesDisabled.get(userId);
+ if (disabled) {
+ // Always log it, as it should only be used on CTS or during development
+ Slog.w(TAG, "getServiceName(): temporary name not set and default disabled for "
+ + "user " + userId);
+ return null;
+ }
+ return getDefaultServiceNameList(userId);
+
+ }
+ }
+
+ /**
+ * Base classes must override this to read from the desired config e.g. framework resource,
+ * secure settings etc.
+ */
+ @Nullable
+ public abstract String[] readServiceNameList(int userId);
+
+ /**
+ * Base classes must override this to read from the desired config e.g. framework resource,
+ * secure settings etc.
+ */
+ @Nullable
+ public abstract String readServiceName(int userId);
+
+ /**
+ * Gets the default list of the service names for the given user.
+ *
+ * <p>Typically implemented by services which want to provide multiple backends.
+ */
+ @Override
+ public String[] getDefaultServiceNameList(int userId) {
+ synchronized (mLock) {
+ if (mIsMultiple) {
+ String[] serviceNameList = readServiceNameList(userId);
+ // Filter out unimplemented services
+ // Initialize the validated array as null because we do not know the final size.
+ List<String> validatedServiceNameList = new ArrayList<>();
+ try {
+ for (int i = 0; i < serviceNameList.length; i++) {
+ if (TextUtils.isEmpty(serviceNameList[i])) {
+ continue;
+ }
+ ComponentName serviceComponent = ComponentName.unflattenFromString(
+ serviceNameList[i]);
+ ServiceInfo serviceInfo = AppGlobals.getPackageManager().getServiceInfo(
+ serviceComponent,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+ if (serviceInfo != null) {
+ validatedServiceNameList.add(serviceNameList[i]);
+ }
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Could not validate provided services.", e);
+ }
+ String[] validatedServiceNameArray = new String[validatedServiceNameList.size()];
+ return validatedServiceNameList.toArray(validatedServiceNameArray);
+ } else {
+ final String name = readServiceName(userId);
+ return TextUtils.isEmpty(name) ? new String[0] : new String[]{name};
+ }
+ }
+ }
+
+ @Override
+ public boolean isConfiguredInMultipleMode() {
+ return mIsMultiple;
+ }
+
+ @Override
+ public boolean isTemporary(@UserIdInt int userId) {
+ synchronized (mLock) {
+ return mTemporaryServiceNamesList.get(userId) != null;
+ }
+ }
+
+ @Override
+ public void setTemporaryService(@UserIdInt int userId, @NonNull String componentName,
+ int durationMs) {
+ setTemporaryServices(userId, new String[]{componentName}, durationMs);
+ }
+
+ @Override
+ public void setTemporaryServices(int userId, @NonNull String[] componentNames, int durationMs) {
+ synchronized (mLock) {
+ mTemporaryServiceNamesList.put(userId, componentNames);
+
+ if (mTemporaryHandler == null) {
+ mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_RESET_TEMPORARY_SERVICE) {
+ synchronized (mLock) {
+ resetTemporaryService(userId);
+ }
+ } else {
+ Slog.wtf(TAG, "invalid handler msg: " + msg);
+ }
+ }
+ };
+ } else {
+ mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
+ }
+ mTemporaryServiceExpiration = SystemClock.elapsedRealtime() + durationMs;
+ mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, durationMs);
+ for (int i = 0; i < componentNames.length; i++) {
+ notifyTemporaryServiceNameChangedLocked(userId, componentNames[i],
+ /* isTemporary= */ true);
+ }
+ }
+ }
+
+ @Override
+ public void resetTemporaryService(@UserIdInt int userId) {
+ synchronized (mLock) {
+ Slog.i(TAG, "resetting temporary service for user " + userId + " from "
+ + Arrays.toString(mTemporaryServiceNamesList.get(userId)));
+ mTemporaryServiceNamesList.remove(userId);
+ if (mTemporaryHandler != null) {
+ mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
+ mTemporaryHandler = null;
+ }
+ notifyTemporaryServiceNameChangedLocked(userId, /* newTemporaryName= */ null,
+ /* isTemporary= */ false);
+ }
+ }
+
+ @Override
+ public boolean setDefaultServiceEnabled(int userId, boolean enabled) {
+ synchronized (mLock) {
+ final boolean currentlyEnabled = isDefaultServiceEnabledLocked(userId);
+ if (currentlyEnabled == enabled) {
+ Slog.i(TAG, "setDefaultServiceEnabled(" + userId + "): already " + enabled);
+ return false;
+ }
+ if (enabled) {
+ Slog.i(TAG, "disabling default service for user " + userId);
+ mDefaultServicesDisabled.removeAt(userId);
+ } else {
+ Slog.i(TAG, "enabling default service for user " + userId);
+ mDefaultServicesDisabled.put(userId, true);
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean isDefaultServiceEnabled(int userId) {
+ synchronized (mLock) {
+ return isDefaultServiceEnabledLocked(userId);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean isDefaultServiceEnabledLocked(int userId) {
+ return !mDefaultServicesDisabled.get(userId);
+ }
+
+ @Override
+ public String toString() {
+ synchronized (mLock) {
+ return "FrameworkResourcesServiceNamer[temps=" + mTemporaryServiceNamesList + "]";
+ }
+ }
+
+ // TODO(b/117779333): support proto
+ @Override
+ public void dumpShort(@NonNull PrintWriter pw, @UserIdInt int userId) {
+ synchronized (mLock) {
+ final String[] temporaryNames = mTemporaryServiceNamesList.get(userId);
+ if (temporaryNames != null) {
+ pw.print("tmpName=");
+ pw.print(Arrays.toString(temporaryNames));
+ final long ttl = mTemporaryServiceExpiration - SystemClock.elapsedRealtime();
+ pw.print(" (expires in ");
+ TimeUtils.formatDuration(ttl, pw);
+ pw.print("), ");
+ }
+ pw.print("defaultName=");
+ pw.print(getDefaultServiceName(userId));
+ final boolean disabled = mDefaultServicesDisabled.get(userId);
+ pw.println(disabled ? " (disabled)" : " (enabled)");
+ }
+ }
+
+ private void notifyTemporaryServiceNameChangedLocked(@UserIdInt int userId,
+ @Nullable String newTemporaryName, boolean isTemporary) {
+ if (mOnSetCallback != null) {
+ mOnSetCallback.onNameResolved(userId, newTemporaryName, isTemporary);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java
index 36199de..9d4f181 100644
--- a/services/core/java/com/android/server/input/BatteryController.java
+++ b/services/core/java/com/android/server/input/BatteryController.java
@@ -371,6 +371,17 @@
}
}
+ public void notifyStylusGestureStarted(int deviceId, long eventTime) {
+ synchronized (mLock) {
+ final DeviceMonitor monitor = mDeviceMonitors.get(deviceId);
+ if (monitor == null) {
+ return;
+ }
+
+ monitor.onStylusGestureStarted(eventTime);
+ }
+ }
+
public void dump(PrintWriter pw, String prefix) {
synchronized (mLock) {
final String indent = prefix + " ";
@@ -557,6 +568,8 @@
public void onTimeout(long eventTime) {}
+ public void onStylusGestureStarted(long eventTime) {}
+
// Returns the current battery state that can be used to notify listeners BatteryController.
public State getBatteryStateForReporting() {
return new State(mState);
@@ -600,6 +613,22 @@
}
@Override
+ public void onStylusGestureStarted(long eventTime) {
+ processChangesAndNotify(eventTime, (time) -> {
+ final boolean wasValid = mValidityTimeoutCallback != null;
+ if (!wasValid && mState.capacity == 0.f) {
+ // Handle a special case where the USI device reports a battery capacity of 0
+ // at boot until the first battery update. To avoid wrongly sending out a
+ // battery capacity of 0 if we detect stylus presence before the capacity
+ // is first updated, do not validate the battery state when the state is not
+ // valid and the capacity is 0.
+ return;
+ }
+ markUsiBatteryValid();
+ });
+ }
+
+ @Override
public void onTimeout(long eventTime) {
processChangesAndNotify(eventTime, (time) -> markUsiBatteryInvalid());
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 69b0e65..31f63d8 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -19,6 +19,8 @@
import static android.provider.DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT;
import static android.view.KeyEvent.KEYCODE_UNKNOWN;
+import android.Manifest;
+import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManagerInternal;
@@ -2671,6 +2673,12 @@
mBatteryController.unregisterBatteryListener(deviceId, listener, Binder.getCallingPid());
}
+ @EnforcePermission(Manifest.permission.BLUETOOTH)
+ @Override
+ public String getInputDeviceBluetoothAddress(int deviceId) {
+ return mNative.getBluetoothAddress(deviceId);
+ }
+
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
@@ -3052,6 +3060,12 @@
com.android.internal.R.bool.config_perDisplayFocusEnabled);
}
+ // Native callback.
+ @SuppressWarnings("unused")
+ private void notifyStylusGestureStarted(int deviceId, long eventTime) {
+ mBatteryController.notifyStylusGestureStarted(deviceId, eventTime);
+ }
+
/**
* Flatten a map into a string list, with value positioned directly next to the
* key.
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 63c0a88..cfa7fb1 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -204,6 +204,9 @@
/** Set the displayId on which the mouse cursor should be shown. */
void setPointerDisplayId(int displayId);
+ /** Get the bluetooth address of an input device if known, otherwise return null. */
+ String getBluetoothAddress(int deviceId);
+
/** The native implementation of InputManagerService methods. */
class NativeImpl implements NativeInputManagerService {
/** Pointer to native input manager service object, used by native code. */
@@ -418,5 +421,8 @@
@Override
public native void setPointerDisplayId(int displayId);
+
+ @Override
+ public native String getBluetoothAddress(int deviceId);
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 76495b1..9cb8f43 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4601,12 +4601,6 @@
}
if (!setVisible) {
if (mCurClient != null) {
- // IMMS only knows of focused window, not the actual IME target.
- // e.g. it isn't aware of any window that has both
- // NOT_FOCUSABLE, ALT_FOCUSABLE_IM flags set and can the IME target.
- // Send it to window manager to hide IME from IME target window.
- // TODO(b/139861270): send to mCurClient.client once IMMS is aware of
- // actual IME target.
mWindowManagerInternal.hideIme(
mHideRequestWindowMap.get(windowToken),
mCurClient.mSelfReportedDisplayId);
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 364f6db..4ce0320 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -364,16 +364,19 @@
// 1.) A normal, non-privileged app querying its own locale.
// 2.) The installer of the given app querying locales of a package installed by said
// installer.
- // 3.) The current input method querying locales of another package.
+ // 3.) The current input method querying locales of the current foreground app.
// 4.) A privileged system service querying locales of another package.
// The least privileged case is a normal app performing a query, so check that first and get
// locales if the package name is owned by the app. Next check if the calling app is the
// installer of the given app and get locales. Finally check if the calling app is the
- // current input method. If neither conditions matched, check if the caller has the
- // necessary permission and fetch locales.
+ // current input method, and that app is querying locales of the current foreground app. If
+ // neither conditions matched, check if the caller has the necessary permission and fetch
+ // locales.
if (!isPackageOwnedByCaller(appPackageName, userId)
&& !isCallerInstaller(appPackageName, userId)
- && !isCallerFromCurrentInputMethod(userId)) {
+ && !(isCallerFromCurrentInputMethod(userId)
+ && mActivityManagerInternal.isAppForeground(
+ getPackageUid(appPackageName, userId)))) {
enforceReadAppSpecificLocalesPermission();
}
final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index f653b93..439e9bd 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -132,7 +132,7 @@
}
}
- mEventLogger.log(new EventLogger.StringEvent("mScreenOnOffReceiver", null));
+ mEventLogger.enqueue(new EventLogger.StringEvent("mScreenOnOffReceiver", null));
}
};
@@ -634,7 +634,7 @@
/* package */ void updateRunningUserAndProfiles(int newActiveUserId) {
synchronized (mLock) {
if (mCurrentActiveUserId != newActiveUserId) {
- mEventLogger.log(
+ mEventLogger.enqueue(
EventLogger.StringEvent.from("switchUser",
"userId: %d", newActiveUserId));
@@ -705,7 +705,7 @@
obtainMessage(UserHandler::notifyRouterRegistered,
userRecord.mHandler, routerRecord));
- mEventLogger.log(EventLogger.StringEvent.from("registerRouter2",
+ mEventLogger.enqueue(EventLogger.StringEvent.from("registerRouter2",
"package: %s, uid: %d, pid: %d, router id: %d",
packageName, uid, pid, routerRecord.mRouterId));
}
@@ -718,7 +718,7 @@
return;
}
- mEventLogger.log(
+ mEventLogger.enqueue(
EventLogger.StringEvent.from(
"unregisterRouter2",
"package: %s, router id: %d",
@@ -744,7 +744,7 @@
return;
}
- mEventLogger.log(EventLogger.StringEvent.from(
+ mEventLogger.enqueue(EventLogger.StringEvent.from(
"setDiscoveryRequestWithRouter2",
"router id: %d, discovery request: %s",
routerRecord.mRouterId, discoveryRequest.toString()));
@@ -766,7 +766,7 @@
RouterRecord routerRecord = mAllRouterRecords.get(binder);
if (routerRecord != null) {
- mEventLogger.log(EventLogger.StringEvent.from(
+ mEventLogger.enqueue(EventLogger.StringEvent.from(
"setRouteVolumeWithRouter2",
"router id: %d, volume: %d",
routerRecord.mRouterId, volume));
@@ -851,7 +851,7 @@
return;
}
- mEventLogger.log(EventLogger.StringEvent.from(
+ mEventLogger.enqueue(EventLogger.StringEvent.from(
"selectRouteWithRouter2",
"router id: %d, route: %s",
routerRecord.mRouterId, route.getId()));
@@ -871,7 +871,7 @@
return;
}
- mEventLogger.log(EventLogger.StringEvent.from(
+ mEventLogger.enqueue(EventLogger.StringEvent.from(
"deselectRouteWithRouter2",
"router id: %d, route: %s",
routerRecord.mRouterId, route.getId()));
@@ -891,7 +891,7 @@
return;
}
- mEventLogger.log(EventLogger.StringEvent.from(
+ mEventLogger.enqueue(EventLogger.StringEvent.from(
"transferToRouteWithRouter2",
"router id: %d, route: %s",
routerRecord.mRouterId, route.getId()));
@@ -921,7 +921,7 @@
return;
}
- mEventLogger.log(EventLogger.StringEvent.from(
+ mEventLogger.enqueue(EventLogger.StringEvent.from(
"setSessionVolumeWithRouter2",
"router id: %d, session: %s, volume: %d",
routerRecord.mRouterId, uniqueSessionId, volume));
@@ -941,7 +941,7 @@
return;
}
- mEventLogger.log(EventLogger.StringEvent.from(
+ mEventLogger.enqueue(EventLogger.StringEvent.from(
"releaseSessionWithRouter2",
"router id: %d, session: %s",
routerRecord.mRouterId, uniqueSessionId));
@@ -983,7 +983,7 @@
return;
}
- mEventLogger.log(
+ mEventLogger.enqueue(
EventLogger.StringEvent.from("registerManager",
"uid: %d, pid: %d, package: %s, userId: %d",
uid, pid, packageName, userId));
@@ -1025,7 +1025,7 @@
}
UserRecord userRecord = managerRecord.mUserRecord;
- mEventLogger.log(
+ mEventLogger.enqueue(
EventLogger.StringEvent.from(
"unregisterManager",
"package: %s, userId: %d, managerId: %d",
@@ -1045,7 +1045,7 @@
return;
}
- mEventLogger.log(
+ mEventLogger.enqueue(
EventLogger.StringEvent.from("startScan",
"manager: %d", managerRecord.mManagerId));
@@ -1059,7 +1059,7 @@
return;
}
- mEventLogger.log(
+ mEventLogger.enqueue(
EventLogger.StringEvent.from("stopScan",
"manager: %d", managerRecord.mManagerId));
@@ -1076,7 +1076,7 @@
return;
}
- mEventLogger.log(
+ mEventLogger.enqueue(
EventLogger.StringEvent.from("setRouteVolumeWithManager",
"managerId: %d, routeId: %s, volume: %d",
managerRecord.mManagerId, route.getId(), volume));
@@ -1096,7 +1096,7 @@
return;
}
- mEventLogger.log(
+ mEventLogger.enqueue(
EventLogger.StringEvent.from("requestCreateSessionWithManager",
"managerId: %d, routeId: %s",
managerRecord.mManagerId, route.getId()));
@@ -1146,7 +1146,7 @@
return;
}
- mEventLogger.log(
+ mEventLogger.enqueue(
EventLogger.StringEvent.from("selectRouteWithManager",
"managerId: %d, session: %s, routeId: %s",
managerRecord.mManagerId, uniqueSessionId, route.getId()));
@@ -1172,7 +1172,7 @@
return;
}
- mEventLogger.log(
+ mEventLogger.enqueue(
EventLogger.StringEvent.from("deselectRouteWithManager",
"managerId: %d, session: %s, routeId: %s",
managerRecord.mManagerId, uniqueSessionId, route.getId()));
@@ -1198,7 +1198,7 @@
return;
}
- mEventLogger.log(
+ mEventLogger.enqueue(
EventLogger.StringEvent.from("transferToRouteWithManager",
"managerId: %d, session: %s, routeId: %s",
managerRecord.mManagerId, uniqueSessionId, route.getId()));
@@ -1224,7 +1224,7 @@
return;
}
- mEventLogger.log(
+ mEventLogger.enqueue(
EventLogger.StringEvent.from("setSessionVolumeWithManager",
"managerId: %d, session: %s, volume: %d",
managerRecord.mManagerId, uniqueSessionId, volume));
@@ -1245,7 +1245,7 @@
return;
}
- mEventLogger.log(
+ mEventLogger.enqueue(
EventLogger.StringEvent.from("releaseSessionWithManager",
"managerId: %d, session: %s",
managerRecord.mManagerId, uniqueSessionId));
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
new file mode 100644
index 0000000..df95f86
--- /dev/null
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2022 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.pm;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.IBackgroundInstallControlService;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.SparseArrayMap;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemService;
+
+/**
+ * @hide
+ */
+public class BackgroundInstallControlService extends SystemService {
+ private static final String TAG = "BackgroundInstallControlService";
+
+ private final Context mContext;
+ private final BinderService mBinderService;
+ private final IPackageManager mIPackageManager;
+
+ // User ID -> package name -> time diff
+ // The time diff between the last foreground activity installer and
+ // the "onPackageAdded" function call.
+ private final SparseArrayMap<String, Long> mBackgroundInstalledPackages =
+ new SparseArrayMap<>();
+
+ public BackgroundInstallControlService(@NonNull Context context) {
+ this(new InjectorImpl(context));
+ }
+
+ @VisibleForTesting
+ BackgroundInstallControlService(@NonNull Injector injector) {
+ super(injector.getContext());
+ mContext = injector.getContext();
+ mIPackageManager = injector.getIPackageManager();
+ mBinderService = new BinderService(this);
+ }
+
+ private static final class BinderService extends IBackgroundInstallControlService.Stub {
+ final BackgroundInstallControlService mService;
+
+ BinderService(BackgroundInstallControlService service) {
+ mService = service;
+ }
+
+ @Override
+ public ParceledListSlice<PackageInfo> getBackgroundInstalledPackages(
+ @PackageManager.PackageInfoFlagsBits long flags, int userId) {
+ ParceledListSlice<PackageInfo> packages;
+ try {
+ packages = mService.mIPackageManager.getInstalledPackages(flags, userId);
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Package manager not available", e);
+ }
+
+ // TODO(b/244216300): to enable the test the usage by BinaryTransparencyService,
+ // we currently comment out the actual implementation.
+ // The fake implementation is just to filter out the first app of the list.
+ // for (int i = 0, size = packages.getList().size(); i < size; i++) {
+ // String packageName = packages.getList().get(i).packageName;
+ // if (!mBackgroundInstalledPackages.contains(userId, packageName) {
+ // packages.getList().remove(i);
+ // }
+ // }
+ if (packages.getList().size() > 0) {
+ packages.getList().remove(0);
+ }
+ return packages;
+ }
+ }
+
+ /**
+ * Called when the system service should publish a binder service using
+ * {@link #publishBinderService(String, IBinder).}
+ */
+ @Override
+ public void onStart() {
+ publishBinderService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE, mBinderService);
+ }
+
+ /**
+ * Dependency injector for {@link #BackgroundInstallControlService)}.
+ */
+ interface Injector {
+ Context getContext();
+
+ IPackageManager getIPackageManager();
+ }
+
+ private static final class InjectorImpl implements Injector {
+ private final Context mContext;
+
+ InjectorImpl(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public IPackageManager getIPackageManager() {
+ return IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/OWNERS b/services/core/java/com/android/server/pm/OWNERS
index 8534fab..84324f2 100644
--- a/services/core/java/com/android/server/pm/OWNERS
+++ b/services/core/java/com/android/server/pm/OWNERS
@@ -3,8 +3,6 @@
jsharkey@android.com
jsharkey@google.com
narayan@google.com
-svetoslavganov@android.com
-svetoslavganov@google.com
include /PACKAGE_MANAGER_OWNERS
# apex support
@@ -26,16 +24,10 @@
per-file PackageUsage.java = file:dex/OWNERS
# multi user / cross profile
-per-file CrossProfileAppsServiceImpl.java = omakoto@google.com, yamasani@google.com
-per-file CrossProfileAppsService.java = omakoto@google.com, yamasani@google.com
-per-file CrossProfileIntentFilter.java = omakoto@google.com, yamasani@google.com
-per-file CrossProfileIntentResolver.java = omakoto@google.com, yamasani@google.com
+per-file CrossProfile* = file:MULTIUSER_AND_ENTERPRISE_OWNERS
per-file RestrictionsSet.java = file:MULTIUSER_AND_ENTERPRISE_OWNERS
-per-file UserManager* = file:/MULTIUSER_OWNERS
per-file UserRestriction* = file:MULTIUSER_AND_ENTERPRISE_OWNERS
-per-file UserSystemPackageInstaller* = file:/MULTIUSER_OWNERS
-per-file UserTypeDetails.java = file:/MULTIUSER_OWNERS
-per-file UserTypeFactory.java = file:/MULTIUSER_OWNERS
+per-file User* = file:/MULTIUSER_OWNERS
# security
per-file KeySetHandle.java = cbrubaker@google.com, nnk@google.com
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 2bfee8c..cf0ea43 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -104,7 +104,6 @@
import android.util.TimeUtils;
import android.util.TypedValue;
import android.util.Xml;
-import android.view.Display;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -625,14 +624,7 @@
@GuardedBy("mUserStates")
private final WatchedUserStates mUserStates = new WatchedUserStates();
- /**
- * Set on on devices that support background users (key) running on secondary displays (value).
- */
- // TODO(b/244644281): move such logic to a different class (like UserDisplayAssigner)
- @Nullable
- @GuardedBy("mUsersOnSecondaryDisplays")
- private final SparseIntArray mUsersOnSecondaryDisplays;
- private final boolean mUsersOnSecondaryDisplaysEnabled;
+ private final UserVisibilityMediator mUserVisibilityMediator;
private static UserManagerService sInstance;
@@ -708,8 +700,7 @@
@VisibleForTesting
UserManagerService(Context context) {
this(context, /* pm= */ null, /* userDataPreparer= */ null,
- /* packagesLock= */ new Object(), context.getCacheDir(), /* users= */ null,
- /* usersOnSecondaryDisplays= */ null);
+ /* packagesLock= */ new Object(), context.getCacheDir(), /* users= */ null);
}
/**
@@ -720,13 +711,13 @@
UserManagerService(Context context, PackageManagerService pm, UserDataPreparer userDataPreparer,
Object packagesLock) {
this(context, pm, userDataPreparer, packagesLock, Environment.getDataDirectory(),
- /* users= */ null, /* usersOnSecondaryDisplays= */ null);
+ /* users= */ null);
}
@VisibleForTesting
UserManagerService(Context context, PackageManagerService pm,
UserDataPreparer userDataPreparer, Object packagesLock, File dataDir,
- SparseArray<UserData> users, @Nullable SparseIntArray usersOnSecondaryDisplays) {
+ SparseArray<UserData> users) {
mContext = context;
mPm = pm;
mPackagesLock = packagesLock;
@@ -756,14 +747,7 @@
mUserStates.put(UserHandle.USER_SYSTEM, UserState.STATE_BOOTING);
mUser0Allocations = DBG_ALLOCATION ? new AtomicInteger() : null;
emulateSystemUserModeIfNeeded();
- mUsersOnSecondaryDisplaysEnabled = UserManager.isUsersOnSecondaryDisplaysEnabled();
- if (mUsersOnSecondaryDisplaysEnabled) {
- mUsersOnSecondaryDisplays = usersOnSecondaryDisplays == null
- ? new SparseIntArray() // default behavior
- : usersOnSecondaryDisplays; // passed by unit test
- } else {
- mUsersOnSecondaryDisplays = null;
- }
+ mUserVisibilityMediator = new UserVisibilityMediator(this);
}
void systemReady() {
@@ -1652,7 +1636,8 @@
return isProfileUnchecked(userId);
}
- private boolean isProfileUnchecked(@UserIdInt int userId) {
+ // TODO(b/244644281): make it private once UserVisibilityMediator don't use it anymore
+ boolean isProfileUnchecked(@UserIdInt int userId) {
synchronized (mUsersLock) {
UserInfo userInfo = getUserInfoLU(userId);
return userInfo != null && userInfo.isProfile();
@@ -1729,11 +1714,6 @@
return userId == getCurrentUserId();
}
- @VisibleForTesting
- boolean isUsersOnSecondaryDisplaysEnabled() {
- return mUsersOnSecondaryDisplaysEnabled;
- }
-
@Override
public boolean isUserVisible(@UserIdInt int userId) {
int callingUserId = UserHandle.getCallingUserId();
@@ -1744,69 +1724,7 @@
+ ") is visible");
}
- return isUserVisibleUnchecked(userId);
- }
-
- @VisibleForTesting
- boolean isUserVisibleUnchecked(@UserIdInt int userId) {
- // First check current foreground user and their profiles (on main display)
- if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
- return true;
- }
-
- // Device doesn't support multiple users on multiple displays, so only users checked above
- // can be visible
- if (!mUsersOnSecondaryDisplaysEnabled) {
- return false;
- }
-
- synchronized (mUsersOnSecondaryDisplays) {
- return mUsersOnSecondaryDisplays.indexOfKey(userId) >= 0;
- }
- }
-
- @VisibleForTesting
- int getDisplayAssignedToUser(@UserIdInt int userId) {
- if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
- return Display.DEFAULT_DISPLAY;
- }
-
- if (!mUsersOnSecondaryDisplaysEnabled) {
- return Display.INVALID_DISPLAY;
- }
-
- synchronized (mUsersOnSecondaryDisplays) {
- return mUsersOnSecondaryDisplays.get(userId, Display.INVALID_DISPLAY);
- }
- }
-
- @VisibleForTesting
- int getUserAssignedToDisplay(int displayId) {
- if (displayId == Display.DEFAULT_DISPLAY || !mUsersOnSecondaryDisplaysEnabled) {
- return getCurrentUserId();
- }
-
- synchronized (mUsersOnSecondaryDisplays) {
- for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
- if (mUsersOnSecondaryDisplays.valueAt(i) != displayId) {
- continue;
- }
- int userId = mUsersOnSecondaryDisplays.keyAt(i);
- if (!isProfileUnchecked(userId)) {
- return userId;
- } else if (DBG_MUMD) {
- Slogf.d(LOG_TAG, "getUserAssignedToDisplay(%d): skipping user %d because it's "
- + "a profile", displayId, userId);
- }
- }
- }
-
- int currentUserId = getCurrentUserId();
- if (DBG_MUMD) {
- Slogf.d(LOG_TAG, "getUserAssignedToDisplay(%d): no user assigned to display, returning "
- + "current user (%d) instead", displayId, currentUserId);
- }
- return currentUserId;
+ return mUserVisibilityMediator.isUserVisible(userId);
}
/**
@@ -1850,54 +1768,9 @@
return false;
}
- // TODO(b/239982558): try to merge with isUserVisibleUnchecked() (once both are unit tested)
- /**
- * See {@link UserManagerInternal#isUserVisible(int, int)}.
- */
+ // Called by UserManagerServiceShellCommand
boolean isUserVisibleOnDisplay(@UserIdInt int userId, int displayId) {
- if (displayId == Display.INVALID_DISPLAY) {
- return false;
- }
- if (!mUsersOnSecondaryDisplaysEnabled) {
- return isCurrentUserOrRunningProfileOfCurrentUser(userId);
- }
-
- // TODO(b/244644281): temporary workaround to let WM use this API without breaking current
- // behavior - return true for current user / profile for any display (other than those
- // explicitly assigned to another users), otherwise they wouldn't be able to launch
- // activities on other non-passenger displays, like cluster, display, or virtual displays).
- // In the long-term, it should rely just on mUsersOnSecondaryDisplays, which
- // would be updated by DisplayManagerService when displays are created / initialized.
- if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
- synchronized (mUsersOnSecondaryDisplays) {
- boolean assignedToUser = false;
- boolean assignedToAnotherUser = false;
- for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
- if (mUsersOnSecondaryDisplays.valueAt(i) == displayId) {
- if (mUsersOnSecondaryDisplays.keyAt(i) == userId) {
- assignedToUser = true;
- break;
- } else {
- assignedToAnotherUser = true;
- // Cannot break because it could be assigned to a profile of the user
- // (and we better not assume that the iteration will check for the
- // parent user before its profiles)
- }
- }
- }
- if (DBG_MUMD) {
- Slogf.d(LOG_TAG, "isUserVisibleOnDisplay(%d, %d): assignedToUser=%b, "
- + "assignedToAnotherUser=%b, mUsersOnSecondaryDisplays=%s",
- userId, displayId, assignedToUser, assignedToAnotherUser,
- mUsersOnSecondaryDisplays);
- }
- return assignedToUser || !assignedToAnotherUser;
- }
- }
-
- synchronized (mUsersOnSecondaryDisplays) {
- return mUsersOnSecondaryDisplays.get(userId, Display.INVALID_DISPLAY) == displayId;
- }
+ return mUserVisibilityMediator.isUserVisible(userId, displayId);
}
@Override
@@ -1915,7 +1788,7 @@
for (int i = 0; i < usersSize; i++) {
UserInfo ui = mUsers.valueAt(i).info;
if (!ui.partial && !ui.preCreated && !mRemovingUserIds.get(ui.id)
- && isUserVisibleUnchecked(ui.id)) {
+ && mUserVisibilityMediator.isUserVisible(ui.id)) {
visibleUsers.add(UserHandle.of(ui.id));
}
}
@@ -6251,9 +6124,15 @@
final long nowRealtime = SystemClock.elapsedRealtime();
final StringBuilder sb = new StringBuilder();
- if (args != null && args.length > 0 && args[0].equals("--user")) {
- dumpUser(pw, UserHandle.parseUserArg(args[1]), sb, now, nowRealtime);
- return;
+ if (args != null && args.length > 0) {
+ switch (args[0]) {
+ case "--user":
+ dumpUser(pw, UserHandle.parseUserArg(args[1]), sb, now, nowRealtime);
+ return;
+ case "--visibility-mediator":
+ mUserVisibilityMediator.dump(pw);
+ return;
+ }
}
final int currentUserId = getCurrentUserId();
@@ -6316,18 +6195,9 @@
}
} // synchronized (mPackagesLock)
- // Multiple Users on Multiple Display info
- pw.println(" Supports users on secondary displays: " + mUsersOnSecondaryDisplaysEnabled);
- // mUsersOnSecondaryDisplaysEnabled is set on constructor, while the UserManager API is
- // set dynamically, so print both to help cases where the developer changed it on the fly
- pw.println(" UM.isUsersOnSecondaryDisplaysEnabled(): "
- + UserManager.isUsersOnSecondaryDisplaysEnabled());
- if (mUsersOnSecondaryDisplaysEnabled) {
- pw.print(" Users on secondary displays: ");
- synchronized (mUsersOnSecondaryDisplays) {
- pw.println(mUsersOnSecondaryDisplays);
- }
- }
+ pw.println();
+ mUserVisibilityMediator.dump(pw);
+ pw.println();
// Dump some capabilities
pw.println();
@@ -6899,133 +6769,33 @@
}
@Override
- public void assignUserToDisplay(int userId, int displayId) {
- if (DBG_MUMD) {
- Slogf.d(LOG_TAG, "assignUserToDisplay(%d, %d)", userId, displayId);
- }
-
- // NOTE: Using Boolean instead of boolean as it will be re-used below
- Boolean isProfile = null;
- if (displayId == Display.DEFAULT_DISPLAY) {
- if (mUsersOnSecondaryDisplaysEnabled) {
- // Profiles are only supported in the default display, but it cannot return yet
- // as it needs to check if the parent is also assigned to the DEFAULT_DISPLAY
- // (this is done indirectly below when it checks that the profile parent is the
- // current user, as the current user is always assigned to the DEFAULT_DISPLAY).
- isProfile = isProfileUnchecked(userId);
- }
- if (isProfile == null || !isProfile) {
- // Don't need to do anything because methods (such as isUserVisible()) already
- // know that the current user (and their profiles) is assigned to the default
- // display.
- if (DBG_MUMD) {
- Slogf.d(LOG_TAG, "ignoring on default display");
- }
- return;
- }
- }
-
- if (!mUsersOnSecondaryDisplaysEnabled) {
- throw new UnsupportedOperationException("assignUserToDisplay(" + userId + ", "
- + displayId + ") called on device that doesn't support multiple "
- + "users on multiple displays");
- }
-
- Preconditions.checkArgument(userId != UserHandle.USER_SYSTEM, "Cannot assign system "
- + "user to secondary display (%d)", displayId);
- Preconditions.checkArgument(displayId != Display.INVALID_DISPLAY,
- "Cannot assign to INVALID_DISPLAY (%d)", displayId);
-
- int currentUserId = getCurrentUserId();
- Preconditions.checkArgument(userId != currentUserId,
- "Cannot assign current user (%d) to other displays", currentUserId);
-
- if (isProfile == null) {
- isProfile = isProfileUnchecked(userId);
- }
- synchronized (mUsersOnSecondaryDisplays) {
- if (isProfile) {
- // Profile can only start in the same display as parent. And for simplicity,
- // that display must be the DEFAULT_DISPLAY.
- Preconditions.checkArgument(displayId == Display.DEFAULT_DISPLAY,
- "Profile user can only be started in the default display");
- int parentUserId = getProfileParentId(userId);
- Preconditions.checkArgument(parentUserId == currentUserId,
- "Only profile of current user can be assigned to a display");
- if (DBG_MUMD) {
- Slogf.d(LOG_TAG, "Ignoring profile user %d on default display", userId);
- }
- return;
- }
-
- // Check if display is available
- for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
- int assignedUserId = mUsersOnSecondaryDisplays.keyAt(i);
- int assignedDisplayId = mUsersOnSecondaryDisplays.valueAt(i);
- if (DBG_MUMD) {
- Slogf.d(LOG_TAG, "%d: assignedUserId=%d, assignedDisplayId=%d",
- i, assignedUserId, assignedDisplayId);
- }
- if (displayId == assignedDisplayId) {
- throw new IllegalStateException("Cannot assign user " + userId + " to "
- + "display " + displayId + " because such display is already "
- + "assigned to user " + assignedUserId);
- }
- if (userId == assignedUserId) {
- throw new IllegalStateException("Cannot assign user " + userId + " to "
- + "display " + displayId + " because such user is as already "
- + "assigned to display " + assignedDisplayId);
- }
- }
-
- if (DBG_MUMD) {
- Slogf.d(LOG_TAG, "Adding full user %d -> display %d", userId, displayId);
- }
- mUsersOnSecondaryDisplays.put(userId, displayId);
- }
+ public void assignUserToDisplay(@UserIdInt int userId, int displayId) {
+ mUserVisibilityMediator.assignUserToDisplay(userId, displayId);
}
@Override
public void unassignUserFromDisplay(@UserIdInt int userId) {
- if (DBG_MUMD) {
- Slogf.d(LOG_TAG, "unassignUserFromDisplay(%d)", userId);
- }
- if (!mUsersOnSecondaryDisplaysEnabled) {
- // Don't need to do anything because methods (such as isUserVisible()) already know
- // that the current user (and their profiles) is assigned to the default display.
- if (DBG_MUMD) {
- Slogf.d(LOG_TAG, "ignoring when device doesn't support MUMD");
- }
- return;
- }
-
- synchronized (mUsersOnSecondaryDisplays) {
- if (DBG_MUMD) {
- Slogf.d(LOG_TAG, "Removing %d from mUsersOnSecondaryDisplays (%s)", userId,
- mUsersOnSecondaryDisplays);
- }
- mUsersOnSecondaryDisplays.delete(userId);
- }
+ mUserVisibilityMediator.unassignUserFromDisplay(userId);
}
@Override
- public boolean isUserVisible(int userId) {
- return isUserVisibleUnchecked(userId);
+ public boolean isUserVisible(@UserIdInt int userId) {
+ return mUserVisibilityMediator.isUserVisible(userId);
}
@Override
- public boolean isUserVisible(int userId, int displayId) {
- return isUserVisibleOnDisplay(userId, displayId);
+ public boolean isUserVisible(@UserIdInt int userId, int displayId) {
+ return mUserVisibilityMediator.isUserVisible(userId, displayId);
}
@Override
- public int getDisplayAssignedToUser(int userId) {
- return UserManagerService.this.getDisplayAssignedToUser(userId);
+ public int getDisplayAssignedToUser(@UserIdInt int userId) {
+ return mUserVisibilityMediator.getDisplayAssignedToUser(userId);
}
@Override
- public int getUserAssignedToDisplay(int displayId) {
- return UserManagerService.this.getUserAssignedToDisplay(displayId);
+ public @UserIdInt int getUserAssignedToDisplay(int displayId) {
+ return mUserVisibilityMediator.getUserAssignedToDisplay(displayId);
}
} // class LocalService
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
new file mode 100644
index 0000000..f725c48
--- /dev/null
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2022 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.pm;
+
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.IndentingPrintWriter;
+import android.util.SparseIntArray;
+import android.view.Display;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+import com.android.server.utils.Slogf;
+
+import java.io.PrintWriter;
+
+/**
+ * Class responsible for deciding whether a user is visible (or visible for a given display).
+ *
+ * <p>This class is thread safe.
+ */
+// TODO(b/244644281): improve javadoc (for example, explain all cases / modes)
+public final class UserVisibilityMediator {
+
+ private static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
+
+ private static final String TAG = UserVisibilityMediator.class.getSimpleName();
+
+ private final Object mLock = new Object();
+
+ // TODO(b/244644281): should not depend on service, but keep its own internal state (like
+ // current user and profile groups), but it is initially as the code was just moved from UMS
+ // "as is". Similarly, it shouldn't need to pass the SparseIntArray on constructor (which was
+ // added to UMS for testing purposes)
+ private final UserManagerService mService;
+
+ private final boolean mUsersOnSecondaryDisplaysEnabled;
+
+ @Nullable
+ @GuardedBy("mLock")
+ private final SparseIntArray mUsersOnSecondaryDisplays;
+
+ UserVisibilityMediator(UserManagerService service) {
+ this(service, UserManager.isUsersOnSecondaryDisplaysEnabled(),
+ /* usersOnSecondaryDisplays= */ null);
+ }
+
+ @VisibleForTesting
+ UserVisibilityMediator(UserManagerService service, boolean usersOnSecondaryDisplaysEnabled,
+ @Nullable SparseIntArray usersOnSecondaryDisplays) {
+ mService = service;
+ mUsersOnSecondaryDisplaysEnabled = usersOnSecondaryDisplaysEnabled;
+ if (mUsersOnSecondaryDisplaysEnabled) {
+ mUsersOnSecondaryDisplays = usersOnSecondaryDisplays == null
+ ? new SparseIntArray() // default behavior
+ : usersOnSecondaryDisplays; // passed by unit test
+ } else {
+ mUsersOnSecondaryDisplays = null;
+ }
+ }
+
+ /**
+ * See {@link UserManagerInternal#assignUserToDisplay(int, int)}.
+ */
+ public void assignUserToDisplay(int userId, int displayId) {
+ if (DBG) {
+ Slogf.d(TAG, "assignUserToDisplay(%d, %d)", userId, displayId);
+ }
+
+ // NOTE: Using Boolean instead of boolean as it will be re-used below
+ Boolean isProfile = null;
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ if (mUsersOnSecondaryDisplaysEnabled) {
+ // Profiles are only supported in the default display, but it cannot return yet
+ // as it needs to check if the parent is also assigned to the DEFAULT_DISPLAY
+ // (this is done indirectly below when it checks that the profile parent is the
+ // current user, as the current user is always assigned to the DEFAULT_DISPLAY).
+ isProfile = isProfileUnchecked(userId);
+ }
+ if (isProfile == null || !isProfile) {
+ // Don't need to do anything because methods (such as isUserVisible()) already
+ // know that the current user (and their profiles) is assigned to the default
+ // display.
+ if (DBG) {
+ Slogf.d(TAG, "ignoring on default display");
+ }
+ return;
+ }
+ }
+
+ if (!mUsersOnSecondaryDisplaysEnabled) {
+ throw new UnsupportedOperationException("assignUserToDisplay(" + userId + ", "
+ + displayId + ") called on device that doesn't support multiple "
+ + "users on multiple displays");
+ }
+
+ Preconditions.checkArgument(userId != UserHandle.USER_SYSTEM, "Cannot assign system "
+ + "user to secondary display (%d)", displayId);
+ Preconditions.checkArgument(displayId != Display.INVALID_DISPLAY,
+ "Cannot assign to INVALID_DISPLAY (%d)", displayId);
+
+ int currentUserId = getCurrentUserId();
+ Preconditions.checkArgument(userId != currentUserId,
+ "Cannot assign current user (%d) to other displays", currentUserId);
+
+ if (isProfile == null) {
+ isProfile = isProfileUnchecked(userId);
+ }
+ synchronized (mLock) {
+ if (isProfile) {
+ // Profile can only start in the same display as parent. And for simplicity,
+ // that display must be the DEFAULT_DISPLAY.
+ Preconditions.checkArgument(displayId == Display.DEFAULT_DISPLAY,
+ "Profile user can only be started in the default display");
+ int parentUserId = getProfileParentId(userId);
+ Preconditions.checkArgument(parentUserId == currentUserId,
+ "Only profile of current user can be assigned to a display");
+ if (DBG) {
+ Slogf.d(TAG, "Ignoring profile user %d on default display", userId);
+ }
+ return;
+ }
+
+ // Check if display is available
+ for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
+ int assignedUserId = mUsersOnSecondaryDisplays.keyAt(i);
+ int assignedDisplayId = mUsersOnSecondaryDisplays.valueAt(i);
+ if (DBG) {
+ Slogf.d(TAG, "%d: assignedUserId=%d, assignedDisplayId=%d",
+ i, assignedUserId, assignedDisplayId);
+ }
+ if (displayId == assignedDisplayId) {
+ throw new IllegalStateException("Cannot assign user " + userId + " to "
+ + "display " + displayId + " because such display is already "
+ + "assigned to user " + assignedUserId);
+ }
+ if (userId == assignedUserId) {
+ throw new IllegalStateException("Cannot assign user " + userId + " to "
+ + "display " + displayId + " because such user is as already "
+ + "assigned to display " + assignedDisplayId);
+ }
+ }
+
+ if (DBG) {
+ Slogf.d(TAG, "Adding full user %d -> display %d", userId, displayId);
+ }
+ mUsersOnSecondaryDisplays.put(userId, displayId);
+ }
+ }
+
+ /**
+ * See {@link UserManagerInternal#unassignUserFromDisplay(int)}.
+ */
+ public void unassignUserFromDisplay(int userId) {
+ if (DBG) {
+ Slogf.d(TAG, "unassignUserFromDisplay(%d)", userId);
+ }
+ if (!mUsersOnSecondaryDisplaysEnabled) {
+ // Don't need to do anything because methods (such as isUserVisible()) already know
+ // that the current user (and their profiles) is assigned to the default display.
+ if (DBG) {
+ Slogf.d(TAG, "ignoring when device doesn't support MUMD");
+ }
+ return;
+ }
+
+ synchronized (mLock) {
+ if (DBG) {
+ Slogf.d(TAG, "Removing %d from mUsersOnSecondaryDisplays (%s)", userId,
+ mUsersOnSecondaryDisplays);
+ }
+ mUsersOnSecondaryDisplays.delete(userId);
+ }
+ }
+
+ /**
+ * See {@link UserManagerInternal#isUserVisible(int)}.
+ */
+ public boolean isUserVisible(int userId) {
+ // First check current foreground user and their profiles (on main display)
+ if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
+ return true;
+ }
+
+ // Device doesn't support multiple users on multiple displays, so only users checked above
+ // can be visible
+ if (!mUsersOnSecondaryDisplaysEnabled) {
+ return false;
+ }
+
+ synchronized (mLock) {
+ return mUsersOnSecondaryDisplays.indexOfKey(userId) >= 0;
+ }
+ }
+
+ /**
+ * See {@link UserManagerInternal#isUserVisible(int, int)}.
+ */
+ public boolean isUserVisible(int userId, int displayId) {
+ if (displayId == Display.INVALID_DISPLAY) {
+ return false;
+ }
+ if (!mUsersOnSecondaryDisplaysEnabled) {
+ return isCurrentUserOrRunningProfileOfCurrentUser(userId);
+ }
+
+ // TODO(b/244644281): temporary workaround to let WM use this API without breaking current
+ // behavior - return true for current user / profile for any display (other than those
+ // explicitly assigned to another users), otherwise they wouldn't be able to launch
+ // activities on other non-passenger displays, like cluster, display, or virtual displays).
+ // In the long-term, it should rely just on mUsersOnSecondaryDisplays, which
+ // would be updated by DisplayManagerService when displays are created / initialized.
+ if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
+ synchronized (mLock) {
+ boolean assignedToUser = false;
+ boolean assignedToAnotherUser = false;
+ for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
+ if (mUsersOnSecondaryDisplays.valueAt(i) == displayId) {
+ if (mUsersOnSecondaryDisplays.keyAt(i) == userId) {
+ assignedToUser = true;
+ break;
+ } else {
+ assignedToAnotherUser = true;
+ // Cannot break because it could be assigned to a profile of the user
+ // (and we better not assume that the iteration will check for the
+ // parent user before its profiles)
+ }
+ }
+ }
+ if (DBG) {
+ Slogf.d(TAG, "isUserVisibleOnDisplay(%d, %d): assignedToUser=%b, "
+ + "assignedToAnotherUser=%b, mUsersOnSecondaryDisplays=%s",
+ userId, displayId, assignedToUser, assignedToAnotherUser,
+ mUsersOnSecondaryDisplays);
+ }
+ return assignedToUser || !assignedToAnotherUser;
+ }
+ }
+
+ synchronized (mLock) {
+ return mUsersOnSecondaryDisplays.get(userId, Display.INVALID_DISPLAY) == displayId;
+ }
+ }
+
+ /**
+ * See {@link UserManagerInternal#getDisplayAssignedToUser(int)}.
+ */
+ public int getDisplayAssignedToUser(int userId) {
+ if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
+ return Display.DEFAULT_DISPLAY;
+ }
+
+ if (!mUsersOnSecondaryDisplaysEnabled) {
+ return Display.INVALID_DISPLAY;
+ }
+
+ synchronized (mLock) {
+ return mUsersOnSecondaryDisplays.get(userId, Display.INVALID_DISPLAY);
+ }
+ }
+
+ /**
+ * See {@link UserManagerInternal#getUserAssignedToDisplay(int)}.
+ */
+ public int getUserAssignedToDisplay(int displayId) {
+ if (displayId == Display.DEFAULT_DISPLAY || !mUsersOnSecondaryDisplaysEnabled) {
+ return getCurrentUserId();
+ }
+
+ synchronized (mLock) {
+ for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
+ if (mUsersOnSecondaryDisplays.valueAt(i) != displayId) {
+ continue;
+ }
+ int userId = mUsersOnSecondaryDisplays.keyAt(i);
+ if (!isProfileUnchecked(userId)) {
+ return userId;
+ } else if (DBG) {
+ Slogf.d(TAG, "getUserAssignedToDisplay(%d): skipping user %d because it's "
+ + "a profile", displayId, userId);
+ }
+ }
+ }
+
+ int currentUserId = getCurrentUserId();
+ if (DBG) {
+ Slogf.d(TAG, "getUserAssignedToDisplay(%d): no user assigned to display, returning "
+ + "current user (%d) instead", displayId, currentUserId);
+ }
+ return currentUserId;
+ }
+
+ private void dump(IndentingPrintWriter ipw) {
+ ipw.println("UserVisibilityManager");
+ ipw.increaseIndent();
+
+ ipw.print("Supports users on secondary displays: ");
+ ipw.println(mUsersOnSecondaryDisplaysEnabled);
+
+ if (mUsersOnSecondaryDisplaysEnabled) {
+ ipw.print("Users on secondary displays: ");
+ synchronized (mLock) {
+ ipw.println(mUsersOnSecondaryDisplays);
+ }
+ }
+
+ ipw.decreaseIndent();
+ }
+
+ void dump(PrintWriter pw) {
+ if (pw instanceof IndentingPrintWriter) {
+ dump((IndentingPrintWriter) pw);
+ return;
+ }
+ dump(new IndentingPrintWriter(pw));
+ }
+
+ // TODO(b/244644281): remove methods below once this class caches that state
+ private @UserIdInt int getCurrentUserId() {
+ return mService.getCurrentUserId();
+ }
+
+ private boolean isCurrentUserOrRunningProfileOfCurrentUser(@UserIdInt int userId) {
+ return mService.isCurrentUserOrRunningProfileOfCurrentUser(userId);
+ }
+
+ private boolean isProfileUnchecked(@UserIdInt int userId) {
+ return mService.isProfileUnchecked(userId);
+ }
+
+ private @UserIdInt int getProfileParentId(@UserIdInt int userId) {
+ return mService.getProfileParentId(userId);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 799ef41..ab223ef 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -4245,7 +4245,6 @@
}
boolean changed = false;
- Set<Permission> needsUpdate = null;
synchronized (mLock) {
final Iterator<Permission> it = mRegistry.getPermissionTrees().iterator();
while (it.hasNext()) {
@@ -4264,26 +4263,6 @@
+ " that used to be declared by " + bp.getPackageName());
it.remove();
}
- if (needsUpdate == null) {
- needsUpdate = new ArraySet<>();
- }
- needsUpdate.add(bp);
- }
- }
- if (needsUpdate != null) {
- for (final Permission bp : needsUpdate) {
- final AndroidPackage sourcePkg =
- mPackageManagerInt.getPackage(bp.getPackageName());
- final PackageStateInternal sourcePs =
- mPackageManagerInt.getPackageStateInternal(bp.getPackageName());
- synchronized (mLock) {
- if (sourcePkg != null && sourcePs != null) {
- continue;
- }
- Slog.w(TAG, "Removing dangling permission tree: " + bp.getName()
- + " from package " + bp.getPackageName());
- mRegistry.removePermission(bp.getName());
- }
}
}
return changed;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index c4e122d..98b5c1b 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -256,6 +256,7 @@
static final int SHORT_PRESS_POWER_GO_HOME = 4;
static final int SHORT_PRESS_POWER_CLOSE_IME_OR_GO_HOME = 5;
static final int SHORT_PRESS_POWER_LOCK_OR_SLEEP = 6;
+ static final int SHORT_PRESS_POWER_DREAM_OR_SLEEP = 7;
// must match: config_LongPressOnPowerBehavior in config.xml
static final int LONG_PRESS_POWER_NOTHING = 0;
@@ -969,7 +970,14 @@
powerMultiPressAction(eventTime, interactive, mTriplePressOnPowerBehavior);
} else if (count > 3 && count <= getMaxMultiPressPowerCount()) {
Slog.d(TAG, "No behavior defined for power press count " + count);
- } else if (count == 1 && interactive && !beganFromNonInteractive) {
+ } else if (count == 1 && interactive) {
+ if (beganFromNonInteractive) {
+ // The "screen is off" case, where we might want to start dreaming on power button
+ // press.
+ attemptToDreamFromShortPowerButtonPress(false, () -> {});
+ return;
+ }
+
if (mSideFpsEventHandler.shouldConsumeSinglePress(eventTime)) {
Slog.i(TAG, "Suppressing power key because the user is interacting with the "
+ "fingerprint sensor");
@@ -1018,11 +1026,39 @@
}
break;
}
+ case SHORT_PRESS_POWER_DREAM_OR_SLEEP: {
+ attemptToDreamFromShortPowerButtonPress(
+ true,
+ () -> sleepDefaultDisplayFromPowerButton(eventTime, 0));
+ break;
+ }
}
}
}
/**
+ * Attempt to dream from a power button press.
+ *
+ * @param isScreenOn Whether the screen is currently on.
+ * @param noDreamAction The action to perform if dreaming is not possible.
+ */
+ private void attemptToDreamFromShortPowerButtonPress(
+ boolean isScreenOn, Runnable noDreamAction) {
+ if (mShortPressOnPowerBehavior != SHORT_PRESS_POWER_DREAM_OR_SLEEP) {
+ noDreamAction.run();
+ return;
+ }
+
+ final DreamManagerInternal dreamManagerInternal = getDreamManagerInternal();
+ if (dreamManagerInternal == null || !dreamManagerInternal.canStartDreaming(isScreenOn)) {
+ noDreamAction.run();
+ return;
+ }
+
+ dreamManagerInternal.requestDream();
+ }
+
+ /**
* Sends the default display to sleep as a result of a power button press.
*
* @return {@code true} if the device was sent to sleep, {@code false} if the device did not
@@ -1593,7 +1629,8 @@
// If there's a dream running then use home to escape the dream
// but don't actually go home.
- if (mDreamManagerInternal != null && mDreamManagerInternal.isDreaming()) {
+ final DreamManagerInternal dreamManagerInternal = getDreamManagerInternal();
+ if (dreamManagerInternal != null && dreamManagerInternal.isDreaming()) {
mDreamManagerInternal.stopDream(false /*immediate*/, "short press on home" /*reason*/);
return;
}
@@ -2529,6 +2566,15 @@
}
}
+ private DreamManagerInternal getDreamManagerInternal() {
+ if (mDreamManagerInternal == null) {
+ // If mDreamManagerInternal is null, attempt to re-fetch it.
+ mDreamManagerInternal = LocalServices.getService(DreamManagerInternal.class);
+ }
+
+ return mDreamManagerInternal;
+ }
+
private void updateWakeGestureListenerLp() {
if (shouldEnableWakeGestureLp()) {
mWakeGestureListener.requestWakeUpTrigger();
diff --git a/services/core/java/com/android/server/utils/EventLogger.java b/services/core/java/com/android/server/utils/EventLogger.java
index 004312f..11766a3 100644
--- a/services/core/java/com/android/server/utils/EventLogger.java
+++ b/services/core/java/com/android/server/utils/EventLogger.java
@@ -43,7 +43,7 @@
/**
* The maximum number of events to keep in {@code mEvents}.
*
- * <p>Calling {@link #log} when the size of {@link #mEvents} matches the threshold will
+ * <p>Calling {@link #enqueue} when the size of {@link #mEvents} matches the threshold will
* cause the oldest event to be evicted.
*/
private final int mMemSize;
@@ -60,7 +60,7 @@
}
/** Enqueues {@code event} to be logged. */
- public synchronized void log(Event event) {
+ public synchronized void enqueue(Event event) {
if (mEvents.size() >= mMemSize) {
mEvents.removeLast();
}
@@ -69,24 +69,14 @@
}
/**
- * Add a string-based event to the log, and print it to logcat as info.
- * @param msg the message for the logs
- * @param tag the logcat tag to use
- */
- public synchronized void loglogi(String msg, String tag) {
- final Event event = new StringEvent(msg);
- log(event.printLog(tag));
- }
-
- /**
- * Same as {@link #loglogi(String, String)} but specifying the logcat type
+ * Add a string-based event to the log, and print it to logcat with a specific severity.
* @param msg the message for the logs
* @param logType the type of logcat entry
* @param tag the logcat tag to use
*/
- public synchronized void loglog(String msg, @Event.LogType int logType, String tag) {
+ public synchronized void enqueueAndLog(String msg, @Event.LogType int logType, String tag) {
final Event event = new StringEvent(msg);
- log(event.printLog(logType, tag));
+ enqueue(event.printLog(logType, tag));
}
/** Dumps events using {@link PrintWriter}. */
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index 7d84bdf..d7c5e93 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -424,7 +424,7 @@
try {
harmfulAppWarning = mService.getPackageManager()
.getHarmfulAppWarning(mAInfo.packageName, mUserId);
- } catch (RemoteException ex) {
+ } catch (RemoteException | IllegalArgumentException ex) {
return false;
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 3f380e7..0d87237 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -107,6 +107,7 @@
jmethodID notifyFocusChanged;
jmethodID notifySensorEvent;
jmethodID notifySensorAccuracy;
+ jmethodID notifyStylusGestureStarted;
jmethodID notifyVibratorState;
jmethodID filterInputEvent;
jmethodID interceptKeyBeforeQueueing;
@@ -299,6 +300,7 @@
void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled);
void setCustomPointerIcon(const SpriteIcon& icon);
void setMotionClassifierEnabled(bool enabled);
+ std::optional<std::string> getBluetoothAddress(int32_t deviceId);
/* --- InputReaderPolicyInterface implementation --- */
@@ -312,6 +314,7 @@
int32_t surfaceRotation) override;
TouchAffineTransformation getTouchAffineTransformation(JNIEnv* env, jfloatArray matrixArr);
+ void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) override;
/* --- InputDispatcherPolicyInterface implementation --- */
@@ -370,37 +373,37 @@
Mutex mLock;
struct Locked {
// Display size information.
- std::vector<DisplayViewport> viewports;
+ std::vector<DisplayViewport> viewports{};
// True if System UI is less noticeable.
- bool systemUiLightsOut;
+ bool systemUiLightsOut{false};
// Pointer speed.
- int32_t pointerSpeed;
+ int32_t pointerSpeed{0};
// Pointer acceleration.
- float pointerAcceleration;
+ float pointerAcceleration{android::os::IInputConstants::DEFAULT_POINTER_ACCELERATION};
// True if pointer gestures are enabled.
- bool pointerGesturesEnabled;
+ bool pointerGesturesEnabled{true};
// Show touches feature enable/disable.
- bool showTouches;
+ bool showTouches{false};
// The latest request to enable or disable Pointer Capture.
- PointerCaptureRequest pointerCaptureRequest;
+ PointerCaptureRequest pointerCaptureRequest{};
// Sprite controller singleton, created on first use.
- sp<SpriteController> spriteController;
+ sp<SpriteController> spriteController{};
// Pointer controller singleton, created and destroyed as needed.
- std::weak_ptr<PointerController> pointerController;
+ std::weak_ptr<PointerController> pointerController{};
// Input devices to be disabled
- std::set<int32_t> disabledInputDevices;
+ std::set<int32_t> disabledInputDevices{};
// Associated Pointer controller display.
- int32_t pointerDisplayId;
+ int32_t pointerDisplayId{ADISPLAY_ID_DEFAULT};
} mLocked GUARDED_BY(mLock);
std::atomic<bool> mInteractive;
@@ -419,16 +422,6 @@
mServiceObj = env->NewGlobalRef(serviceObj);
- {
- AutoMutex _l(mLock);
- mLocked.systemUiLightsOut = false;
- mLocked.pointerSpeed = 0;
- mLocked.pointerAcceleration = android::os::IInputConstants::DEFAULT_POINTER_ACCELERATION;
- mLocked.pointerGesturesEnabled = true;
- mLocked.showTouches = false;
- mLocked.pointerDisplayId = ADISPLAY_ID_DEFAULT;
- }
- mInteractive = true;
InputManager* im = new InputManager(this, this);
mInputManager = im;
defaultServiceManager()->addService(String16("inputflinger"), im);
@@ -1177,6 +1170,13 @@
return transform;
}
+void NativeInputManager::notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) {
+ JNIEnv* env = jniEnv();
+ env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyStylusGestureStarted, deviceId,
+ eventTime);
+ checkAndClearExceptionFromCallback(env, "notifyStylusGestureStarted");
+}
+
bool NativeInputManager::filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) {
ATRACE_CALL();
jobject inputEventObj;
@@ -1487,6 +1487,10 @@
mInputManager->getProcessor().setMotionClassifierEnabled(enabled);
}
+std::optional<std::string> NativeInputManager::getBluetoothAddress(int32_t deviceId) {
+ return mInputManager->getReader().getBluetoothAddress(deviceId);
+}
+
bool NativeInputManager::isPerDisplayTouchModeEnabled() {
JNIEnv* env = jniEnv();
jboolean enabled =
@@ -2326,6 +2330,12 @@
im->setPointerDisplayId(displayId);
}
+static jstring nativeGetBluetoothAddress(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ const auto address = im->getBluetoothAddress(deviceId);
+ return address ? env->NewStringUTF(address->c_str()) : nullptr;
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gInputManagerMethods[] = {
@@ -2408,6 +2418,7 @@
{"flushSensor", "(II)Z", (void*)nativeFlushSensor},
{"cancelCurrentTouch", "()V", (void*)nativeCancelCurrentTouch},
{"setPointerDisplayId", "(I)V", (void*)nativeSetPointerDisplayId},
+ {"getBluetoothAddress", "(I)Ljava/lang/String;", (void*)nativeGetBluetoothAddress},
};
#define FIND_CLASS(var, className) \
@@ -2469,6 +2480,9 @@
GET_METHOD_ID(gServiceClassInfo.notifySensorAccuracy, clazz, "notifySensorAccuracy", "(III)V");
+ GET_METHOD_ID(gServiceClassInfo.notifyStylusGestureStarted, clazz, "notifyStylusGestureStarted",
+ "(IJ)V");
+
GET_METHOD_ID(gServiceClassInfo.notifyVibratorState, clazz, "notifyVibratorState", "(IZ)V");
GET_METHOD_ID(gServiceClassInfo.notifyNoFocusedWindowAnr, clazz, "notifyNoFocusedWindowAnr",
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 91f5c69..40412db 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -21,20 +21,28 @@
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.credentials.CreateCredentialRequest;
import android.credentials.GetCredentialRequest;
import android.credentials.ICreateCredentialCallback;
import android.credentials.ICredentialManager;
import android.credentials.IGetCredentialCallback;
+import android.os.Binder;
import android.os.CancellationSignal;
import android.os.ICancellationSignal;
import android.os.UserHandle;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.Log;
+import android.util.Slog;
import com.android.server.infra.AbstractMasterSystemService;
import com.android.server.infra.SecureSettingsServiceNameResolver;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
/**
* Entry point service for credential management.
*
@@ -49,52 +57,99 @@
public CredentialManagerService(@NonNull Context context) {
super(context,
- new SecureSettingsServiceNameResolver(context, Settings.Secure.AUTOFILL_SERVICE),
+ new SecureSettingsServiceNameResolver(context, Settings.Secure.CREDENTIAL_SERVICE,
+ /*isMultipleMode=*/true),
null, PACKAGE_UPDATE_POLICY_REFRESH_EAGER);
}
@Override
protected String getServiceSettingsProperty() {
- return Settings.Secure.AUTOFILL_SERVICE;
+ return Settings.Secure.CREDENTIAL_SERVICE;
}
@Override // from AbstractMasterSystemService
protected CredentialManagerServiceImpl newServiceLocked(@UserIdInt int resolvedUserId,
boolean disabled) {
- return new CredentialManagerServiceImpl(this, mLock, resolvedUserId);
+ // This method should not be called for CredentialManagerService as it is configured to use
+ // multiple services.
+ Slog.w(TAG, "Should not be here - CredentialManagerService is configured to use "
+ + "multiple services");
+ return null;
}
- @Override
+ @Override // from SystemService
public void onStart() {
- Log.i(TAG, "onStart");
publishBinderService(CREDENTIAL_SERVICE, new CredentialManagerServiceStub());
}
+ @Override // from AbstractMasterSystemService
+ protected List<CredentialManagerServiceImpl> newServiceListLocked(int resolvedUserId,
+ boolean disabled, String[] serviceNames) {
+ if (serviceNames == null || serviceNames.length == 0) {
+ Slog.i(TAG, "serviceNames sent in newServiceListLocked is null, or empty");
+ return new ArrayList<>();
+ }
+ List<CredentialManagerServiceImpl> serviceList = new ArrayList<>(serviceNames.length);
+ for (int i = 0; i < serviceNames.length; i++) {
+ Log.i(TAG, "in newServiceListLocked, service: " + serviceNames[i]);
+ if (TextUtils.isEmpty(serviceNames[i])) {
+ continue;
+ }
+ try {
+ serviceList.add(new CredentialManagerServiceImpl(this, mLock, resolvedUserId,
+ serviceNames[i]));
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.i(TAG, "Unable to add serviceInfo : " + e.getMessage());
+ } catch (SecurityException e) {
+ Log.i(TAG, "Unable to add serviceInfo : " + e.getMessage());
+ }
+ }
+ return serviceList;
+ }
+
+ private void runForUser(@NonNull final Consumer<CredentialManagerServiceImpl> c) {
+ final int userId = UserHandle.getCallingUserId();
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ final List<CredentialManagerServiceImpl> services =
+ getServiceListForUserLocked(userId);
+ services.forEach(s -> {
+ c.accept(s);
+ });
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
final class CredentialManagerServiceStub extends ICredentialManager.Stub {
@Override
public ICancellationSignal executeGetCredential(
GetCredentialRequest request,
- IGetCredentialCallback callback) {
- // TODO: implement.
- Log.i(TAG, "executeGetCredential");
-
- final int userId = UserHandle.getCallingUserId();
- synchronized (mLock) {
- final CredentialManagerServiceImpl service = peekServiceForUserLocked(userId);
- if (service != null) {
- Log.i(TAG, "Got service for : " + userId);
- service.getCredential();
- }
- }
-
+ IGetCredentialCallback callback,
+ final String callingPackage) {
+ Log.i(TAG, "starting executeGetCredential with callingPackage: " + callingPackage);
+ // TODO : Implement cancellation
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+
+ // New request session, scoped for this request only.
+ final GetRequestSession session = new GetRequestSession(getContext(),
+ UserHandle.getCallingUserId(),
+ callback);
+
+ // Invoke all services of a user
+ runForUser((service) -> {
+ service.getCredential(request, session, callingPackage);
+ });
return cancelTransport;
}
@Override
public ICancellationSignal executeCreateCredential(
CreateCredentialRequest request,
- ICreateCredentialCallback callback) {
+ ICreateCredentialCallback callback,
+ String callingPackage) {
// TODO: implement.
Log.i(TAG, "executeCreateCredential");
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
index aa19241..cc03f9b 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
@@ -17,47 +17,94 @@
package com.android.server.credentials;
import android.annotation.NonNull;
-import android.app.AppGlobals;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
-import android.os.RemoteException;
-import android.util.Log;
+import android.credentials.GetCredentialRequest;
+import android.service.credentials.CredentialProviderInfo;
+import android.service.credentials.GetCredentialsRequest;
+import android.util.Slog;
import com.android.server.infra.AbstractPerUserSystemService;
+
/**
- * Per-user implementation of {@link CredentialManagerService}
+ * Per-user, per remote service implementation of {@link CredentialManagerService}
*/
public final class CredentialManagerServiceImpl extends
AbstractPerUserSystemService<CredentialManagerServiceImpl, CredentialManagerService> {
private static final String TAG = "CredManSysServiceImpl";
- protected CredentialManagerServiceImpl(
+ // TODO(b/210531) : Make final when update flow is fixed
+ private ComponentName mRemoteServiceComponentName;
+ private CredentialProviderInfo mInfo;
+
+ public CredentialManagerServiceImpl(
@NonNull CredentialManagerService master,
- @NonNull Object lock, int userId) {
+ @NonNull Object lock, int userId, String serviceName)
+ throws PackageManager.NameNotFoundException {
super(master, lock, userId);
+ Slog.i(TAG, "in CredentialManagerServiceImpl cons");
+ // TODO : Replace with newServiceInfoLocked after confirming behavior
+ mRemoteServiceComponentName = ComponentName.unflattenFromString(serviceName);
+ mInfo = new CredentialProviderInfo(getContext(), mRemoteServiceComponentName, mUserId);
}
@Override // from PerUserSystemService
protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
throws PackageManager.NameNotFoundException {
- ServiceInfo si;
- try {
- si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
- PackageManager.GET_META_DATA, mUserId);
- } catch (RemoteException e) {
- throw new PackageManager.NameNotFoundException(
- "Could not get service for " + serviceComponent);
- }
- return si;
+ // TODO : Test update flows with multiple providers
+ Slog.i(TAG , "newServiceInfoLocked with : " + serviceComponent.getPackageName());
+ mRemoteServiceComponentName = serviceComponent;
+ mInfo = new CredentialProviderInfo(getContext(), serviceComponent, mUserId);
+ return mInfo.getServiceInfo();
}
- /**
- * Unimplemented getCredentials
- */
- public void getCredential() {
- Log.i(TAG, "getCredential not implemented");
- // TODO : Implement logic
+ public void getCredential(GetCredentialRequest request, GetRequestSession requestSession,
+ String callingPackage) {
+ Slog.i(TAG, "in getCredential in CredManServiceImpl");
+ if (mInfo == null) {
+ Slog.i(TAG, "in getCredential in CredManServiceImpl, but mInfo is null");
+ return;
+ }
+
+ // TODO : Determine if remoteService instance can be reused across requests
+ final RemoteCredentialService remoteService = new RemoteCredentialService(
+ getContext(), mInfo.getServiceInfo().getComponentName(), mUserId);
+ ProviderGetSession providerSession = new ProviderGetSession(mInfo,
+ requestSession, mUserId, remoteService);
+ // Set the provider info to the session when the request is initiated. This happens here
+ // because there is one serviceImpl per remote provider, and so we can only retrieve
+ // the provider information in the scope of this instance, whereas the session is for the
+ // entire request.
+ requestSession.addProviderSession(providerSession);
+ GetCredentialsRequest filteredRequest = getRequestWithValidType(request, callingPackage);
+ if (filteredRequest != null) {
+ remoteService.onGetCredentials(getRequestWithValidType(request, callingPackage),
+ providerSession);
+ }
+ }
+
+ @Nullable
+ private GetCredentialsRequest getRequestWithValidType(GetCredentialRequest request,
+ String callingPackage) {
+ GetCredentialsRequest.Builder builder =
+ new GetCredentialsRequest.Builder(callingPackage);
+ request.getGetCredentialOptions().forEach( option -> {
+ if (mInfo.hasCapability(option.getType())) {
+ Slog.i(TAG, "Provider can handle: " + option.getType());
+ builder.addGetCredentialOption(option);
+ } else {
+ Slog.i(TAG, "Skipping request as provider cannot handle it");
+ }
+ });
+
+ try {
+ return builder.build();
+ } catch (IllegalArgumentException | NullPointerException e) {
+ Slog.i(TAG, "issue with request build: " + e.getMessage());
+ }
+ return null;
}
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
new file mode 100644
index 0000000..69fb1ea
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 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.credentials;
+
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.credentials.ui.IntentFactory;
+import android.credentials.ui.ProviderData;
+import android.credentials.ui.RequestInfo;
+import android.credentials.ui.UserSelectionDialogResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ResultReceiver;
+import android.util.Log;
+import android.util.Slog;
+
+import java.util.ArrayList;
+
+/** Initiates the Credential Manager UI and receives results. */
+public class CredentialManagerUi {
+ private static final String TAG = "CredentialManagerUi";
+ @NonNull
+ private final CredentialManagerUiCallback mCallbacks;
+ @NonNull private final Context mContext;
+ private final int mUserId;
+ @NonNull private final ResultReceiver mResultReceiver = new ResultReceiver(
+ new Handler(Looper.getMainLooper())) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ handleUiResult(resultCode, resultData);
+ }
+ };
+
+ private void handleUiResult(int resultCode, Bundle resultData) {
+ if (resultCode == Activity.RESULT_OK) {
+ UserSelectionDialogResult selection = UserSelectionDialogResult
+ .fromResultData(resultData);
+ if (selection != null) {
+ mCallbacks.onUiSelection(selection);
+ } else {
+ Slog.i(TAG, "No selection found in UI result");
+ }
+ } else if (resultCode == Activity.RESULT_CANCELED) {
+ mCallbacks.onUiCancelation();
+ }
+ }
+
+ /**
+ * Interface to be implemented by any class that wishes to get callbacks from the UI.
+ */
+ public interface CredentialManagerUiCallback {
+ /** Called when the user makes a selection. */
+ void onUiSelection(UserSelectionDialogResult selection);
+ /** Called when the user cancels the UI. */
+ void onUiCancelation();
+ }
+ public CredentialManagerUi(Context context, int userId,
+ CredentialManagerUiCallback callbacks) {
+ Log.i(TAG, "In CredentialManagerUi constructor");
+ mContext = context;
+ mUserId = userId;
+ mCallbacks = callbacks;
+ }
+
+ /**
+ * Surfaces the Credential Manager bottom sheet UI.
+ * @param providerDataList the list of provider data from remote providers
+ */
+ public void show(RequestInfo requestInfo, ArrayList<ProviderData> providerDataList) {
+ Log.i(TAG, "In show");
+ Intent intent = IntentFactory.newIntent(requestInfo, providerDataList,
+ mResultReceiver);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+ }
+}
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
new file mode 100644
index 0000000..80f0fec
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2022 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.credentials;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.credentials.Credential;
+import android.credentials.GetCredentialResponse;
+import android.credentials.IGetCredentialCallback;
+import android.credentials.ui.ProviderData;
+import android.credentials.ui.RequestInfo;
+import android.credentials.ui.UserSelectionDialogResult;
+import android.os.RemoteException;
+import android.service.credentials.CredentialEntry;
+import android.util.Log;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Central session for a single getCredentials request. This class listens to the
+ * responses from providers, and the UX app, and updates the provider(S) state.
+ */
+public final class GetRequestSession extends RequestSession {
+ private static final String TAG = "GetRequestSession";
+
+ private final IGetCredentialCallback mClientCallback;
+ private final Map<String, ProviderGetSession> mProviders;
+
+ public GetRequestSession(Context context, int userId,
+ IGetCredentialCallback callback) {
+ super(context, userId, RequestInfo.TYPE_GET);
+ mClientCallback = callback;
+ mProviders = new HashMap<>();
+ }
+
+ /**
+ * Adds a new provider to the list of providers that are contributing to this session.
+ */
+ public void addProviderSession(ProviderGetSession providerSession) {
+ mProviders.put(providerSession.getComponentName().flattenToString(),
+ providerSession);
+ }
+
+ @Override
+ public void onProviderStatusChanged(ProviderSession.Status status,
+ ComponentName componentName) {
+ Log.i(TAG, "in onStatusChanged");
+ if (ProviderSession.isTerminatingStatus(status)) {
+ Log.i(TAG, "in onStatusChanged terminating status");
+
+ ProviderGetSession session = mProviders.remove(componentName.flattenToString());
+ if (session != null) {
+ Slog.i(TAG, "Provider session removed.");
+ } else {
+ Slog.i(TAG, "Provider session null, did not exist.");
+ }
+ } else if (ProviderSession.isCompletionStatus(status)) {
+ Log.i(TAG, "in onStatusChanged isCompletionStatus status");
+ onProviderResponseComplete();
+ }
+ }
+
+ @Override
+ public void onUiSelection(UserSelectionDialogResult selection) {
+ String providerId = selection.getProviderId();
+ ProviderGetSession providerSession = mProviders.get(providerId);
+ if (providerSession != null) {
+ CredentialEntry credentialEntry = providerSession.getCredentialEntry(
+ selection.getEntrySubkey());
+ if (credentialEntry != null && credentialEntry.getCredential() != null) {
+ respondToClientAndFinish(credentialEntry.getCredential());
+ }
+ // TODO : Handle action chips and authentication selection
+ return;
+ }
+ // TODO : finish session and respond to client if provider not found
+ }
+
+ @Override
+ public void onUiCancelation() {
+ // User canceled the activity
+ // TODO : Send error code to client
+ finishSession();
+ }
+
+ private void onProviderResponseComplete() {
+ Log.i(TAG, "in onProviderResponseComplete");
+ if (isResponseCompleteAcrossProviders()) {
+ Log.i(TAG, "in onProviderResponseComplete - isResponseCompleteAcrossProviders");
+ getProviderDataAndInitiateUi();
+ }
+ }
+
+ private void getProviderDataAndInitiateUi() {
+ ArrayList<ProviderData> providerDataList = new ArrayList<>();
+ for (ProviderGetSession session : mProviders.values()) {
+ Log.i(TAG, "preparing data for : " + session.getComponentName());
+ providerDataList.add(session.prepareUiData());
+ }
+ if (!providerDataList.isEmpty()) {
+ Log.i(TAG, "provider list not empty about to initiate ui");
+ initiateUi(providerDataList);
+ }
+ }
+
+ private void initiateUi(ArrayList<ProviderData> providerDataList) {
+ mHandler.post(() -> mCredentialManagerUi.show(RequestInfo.newGetRequestInfo(
+ mRequestId, null, mIsFirstUiTurn, ""),
+ providerDataList));
+ }
+
+ /**
+ * Iterates over all provider sessions and returns true if all have responded.
+ */
+ private boolean isResponseCompleteAcrossProviders() {
+ AtomicBoolean isRequestComplete = new AtomicBoolean(true);
+ mProviders.forEach( (packageName, session) -> {
+ if (session.getStatus() != ProviderSession.Status.COMPLETE) {
+ isRequestComplete.set(false);
+ }
+ });
+ return isRequestComplete.get();
+ }
+
+ private void respondToClientAndFinish(Credential credential) {
+ try {
+ mClientCallback.onResponse(new GetCredentialResponse(credential));
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ finishSession();
+ }
+
+ private void finishSession() {
+ clearProviderSessions();
+ }
+
+ private void clearProviderSessions() {
+ for (ProviderGetSession session : mProviders.values()) {
+ // TODO : Evaluate if we should unbind remote services here or wait for them
+ // to automatically unbind when idle. Re-binding frequently also has a cost.
+ //session.destroy();
+ }
+ mProviders.clear();
+ }
+}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
new file mode 100644
index 0000000..24610df
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2022 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.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.slice.Slice;
+import android.credentials.ui.Entry;
+import android.credentials.ui.ProviderData;
+import android.service.credentials.Action;
+import android.service.credentials.CredentialEntry;
+import android.service.credentials.CredentialProviderInfo;
+import android.service.credentials.CredentialsDisplayContent;
+import android.service.credentials.GetCredentialsResponse;
+import android.util.Log;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * Central provider session that listens for provider callbacks, and maintains provider state.
+ * Will likely split this into remote response state and UI state.
+ */
+public final class ProviderGetSession extends ProviderSession<GetCredentialsResponse>
+ implements RemoteCredentialService.ProviderCallbacks<GetCredentialsResponse> {
+ private static final String TAG = "ProviderGetSession";
+
+ // Key to be used as an entry key for a credential entry
+ private static final String CREDENTIAL_ENTRY_KEY = "credential_key";
+
+ private GetCredentialsResponse mResponse;
+
+ @NonNull
+ private final Map<String, CredentialEntry> mUiCredentials = new HashMap<>();
+
+ @NonNull
+ private final Map<String, Action> mUiActions = new HashMap<>();
+
+ public ProviderGetSession(CredentialProviderInfo info,
+ ProviderInternalCallback callbacks,
+ int userId, RemoteCredentialService remoteCredentialService) {
+ super(info, callbacks, userId, remoteCredentialService);
+ setStatus(Status.PENDING);
+ }
+
+ /** Updates the response being maintained in state by this provider session. */
+ @Override
+ public void updateResponse(GetCredentialsResponse response) {
+ if (response.getAuthenticationAction() != null) {
+ // TODO : Implement authentication logic
+ } else if (response.getCredentialsDisplayContent() != null) {
+ Log.i(TAG , "updateResponse with credentialEntries");
+ mResponse = response;
+ updateStatusAndInvokeCallback(Status.COMPLETE);
+ }
+ }
+
+ /** Returns the response being maintained in this provider session. */
+ @Override
+ @Nullable
+ public GetCredentialsResponse getResponse() {
+ return mResponse;
+ }
+
+ /** Returns the credential entry maintained in state by this provider session. */
+ @Nullable
+ public CredentialEntry getCredentialEntry(@NonNull String entryId) {
+ return mUiCredentials.get(entryId);
+ }
+
+ /** Returns the action entry maintained in state by this provider session. */
+ @Nullable
+ public Action getAction(@NonNull String entryId) {
+ return mUiActions.get(entryId);
+ }
+
+ /** Called when the provider response has been updated by an external source. */
+ @Override
+ public void onProviderResponseSuccess(@Nullable GetCredentialsResponse response) {
+ Log.i(TAG, "in onProviderResponseSuccess");
+ updateResponse(response);
+ }
+
+ /** Called when the provider response resulted in a failure. */
+ @Override
+ public void onProviderResponseFailure(int errorCode, @Nullable CharSequence message) {
+ updateStatusAndInvokeCallback(toStatus(errorCode));
+ }
+
+ /** Called when provider service dies. */
+ @Override
+ public void onProviderServiceDied(RemoteCredentialService service) {
+ if (service.getComponentName().equals(mProviderInfo.getServiceInfo().getComponentName())) {
+ updateStatusAndInvokeCallback(Status.SERVICE_DEAD);
+ } else {
+ Slog.i(TAG, "Component names different in onProviderServiceDied - "
+ + "this should not happen");
+ }
+ }
+
+ @Override
+ protected final ProviderData prepareUiData() throws IllegalArgumentException {
+ Log.i(TAG, "In prepareUiData");
+ if (!ProviderSession.isCompletionStatus(getStatus())) {
+ Log.i(TAG, "In prepareUiData not complete");
+
+ throw new IllegalStateException("Status must be in completion mode");
+ }
+ GetCredentialsResponse response = getResponse();
+ if (response == null) {
+ Log.i(TAG, "In prepareUiData response null");
+
+ throw new IllegalStateException("Response must be in completion mode");
+ }
+ if (response.getAuthenticationAction() != null) {
+ Log.i(TAG, "In prepareUiData auth not null");
+
+ return prepareUiProviderDataWithAuthentication(response.getAuthenticationAction());
+ }
+ if (response.getCredentialsDisplayContent() != null){
+ Log.i(TAG, "In prepareUiData credentials not null");
+
+ return prepareUiProviderDataWithCredentials(response.getCredentialsDisplayContent());
+ }
+ return null;
+ }
+
+ /**
+ * To be called by {@link ProviderGetSession} when the UI is to be invoked.
+ */
+ @Nullable
+ private ProviderData prepareUiProviderDataWithCredentials(@NonNull
+ CredentialsDisplayContent content) {
+ Log.i(TAG, "in prepareUiProviderData");
+ List<Entry> credentialEntries = new ArrayList<>();
+ List<Entry> actionChips = new ArrayList<>();
+ Entry authenticationEntry = null;
+
+ // Populate the credential entries
+ for (CredentialEntry credentialEntry : content.getCredentialEntries()) {
+ String entryId = UUID.randomUUID().toString();
+ mUiCredentials.put(entryId, credentialEntry);
+ Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
+ Slice slice = credentialEntry.getSlice();
+ // TODO : Remove conversion of string to int after change in Entry class
+ credentialEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
+ credentialEntry.getSlice()));
+ }
+ // populate the action chip
+ for (Action action : content.getActions()) {
+ String entryId = UUID.randomUUID().toString();
+ mUiActions.put(entryId, action);
+ // TODO : Remove conversion of string to int after change in Entry class
+ actionChips.add(new Entry(ACTION_ENTRY_KEY, entryId,
+ action.getSlice()));
+ }
+
+ // TODO : Set the correct last used time
+ return new ProviderData.Builder(mComponentName.flattenToString(),
+ mProviderInfo.getServiceLabel() == null ? "" :
+ mProviderInfo.getServiceLabel().toString(),
+ /*icon=*/null)
+ .setCredentialEntries(credentialEntries)
+ .setActionChips(actionChips)
+ .setAuthenticationEntry(authenticationEntry)
+ .setLastUsedTimeMillis(0)
+ .build();
+ }
+
+ /**
+ * To be called by {@link ProviderGetSession} when the UI is to be invoked.
+ */
+ @Nullable
+ private ProviderData prepareUiProviderDataWithAuthentication(@NonNull
+ Action authenticationEntry) {
+ // TODO : Implement authentication flow
+ return null;
+ }
+}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
new file mode 100644
index 0000000..3a9f964
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2022 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.credentials;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.credentials.ui.ProviderData;
+import android.service.credentials.CredentialProviderException;
+import android.service.credentials.CredentialProviderInfo;
+
+/**
+ * Provider session storing the state of provider response and ui entries.
+ * @param <T> The request type expected from the remote provider, for a given request session.
+ */
+public abstract class ProviderSession<T> implements RemoteCredentialService.ProviderCallbacks<T> {
+ // Key to be used as the entry key for an action entry
+ protected static final String ACTION_ENTRY_KEY = "action_key";
+
+ @NonNull protected final ComponentName mComponentName;
+ @NonNull protected final CredentialProviderInfo mProviderInfo;
+ @NonNull protected final RemoteCredentialService mRemoteCredentialService;
+ @NonNull protected final int mUserId;
+ @NonNull protected Status mStatus = Status.NOT_STARTED;
+ @NonNull protected final ProviderInternalCallback mCallbacks;
+
+ /**
+ * Interface to be implemented by any class that wishes to get a callback when a particular
+ * provider session's status changes. Typically, implemented by the {@link RequestSession}
+ * class.
+ */
+ public interface ProviderInternalCallback {
+ /**
+ * Called when status changes.
+ */
+ void onProviderStatusChanged(Status status, ComponentName componentName);
+ }
+
+ protected ProviderSession(@NonNull CredentialProviderInfo info,
+ @NonNull ProviderInternalCallback callbacks,
+ @NonNull int userId,
+ @NonNull RemoteCredentialService remoteCredentialService) {
+ mProviderInfo = info;
+ mCallbacks = callbacks;
+ mUserId = userId;
+ mComponentName = info.getServiceInfo().getComponentName();
+ mRemoteCredentialService = remoteCredentialService;
+ }
+
+ /** Update the response state stored with the provider session. */
+ protected abstract void updateResponse (T response);
+
+ /** Update the response state stored with the provider session. */
+ protected abstract T getResponse ();
+
+ /** Should be overridden to prepare, and stores state for {@link ProviderData} to be
+ * shown on the UI. */
+ protected abstract ProviderData prepareUiData();
+
+ /** Provider status at various states of the request session. */
+ enum Status {
+ NOT_STARTED,
+ PENDING,
+ REQUIRES_AUTHENTICATION,
+ COMPLETE,
+ SERVICE_DEAD,
+ CANCELED
+ }
+
+ protected void setStatus(@NonNull Status status) {
+ mStatus = status;
+ }
+
+ @NonNull
+ protected Status getStatus() {
+ return mStatus;
+ }
+
+ @NonNull
+ protected ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ /** Updates the status .*/
+ protected void updateStatusAndInvokeCallback(@NonNull Status status) {
+ setStatus(status);
+ mCallbacks.onProviderStatusChanged(status, mComponentName);
+ }
+
+ @NonNull
+ public static Status toStatus(
+ @CredentialProviderException.CredentialProviderError int errorCode) {
+ // TODO : Add more mappings as more flows are supported
+ return Status.CANCELED;
+ }
+
+ /**
+ * Returns true if the given status means that the provider session must be terminated.
+ */
+ public static boolean isTerminatingStatus(Status status) {
+ return status == Status.CANCELED || status == Status.SERVICE_DEAD;
+ }
+
+ /**
+ * Returns true if the given status means that the provider is done getting the response,
+ * and is ready for user interaction.
+ */
+ public static boolean isCompletionStatus(Status status) {
+ return status == Status.COMPLETE || status == Status.REQUIRES_AUTHENTICATION;
+ }
+}
diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
new file mode 100644
index 0000000..d0b6e7d
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2022 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.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.ICancellationSignal;
+import android.os.RemoteException;
+import android.service.credentials.CredentialProviderException;
+import android.service.credentials.CredentialProviderException.CredentialProviderError;
+import android.service.credentials.CredentialProviderService;
+import android.service.credentials.GetCredentialsRequest;
+import android.service.credentials.GetCredentialsResponse;
+import android.service.credentials.ICredentialProviderService;
+import android.service.credentials.IGetCredentialsCallback;
+import android.text.format.DateUtils;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.infra.ServiceConnector;
+
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Handles connections with the remote credential provider
+ *
+ * @hide
+ */
+public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialProviderService>{
+
+ private static final String TAG = "RemoteCredentialService";
+ /** Timeout for a single request. */
+ private static final long TIMEOUT_REQUEST_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
+ /** Timeout to unbind after the task queue is empty. */
+ private static final long TIMEOUT_IDLE_SERVICE_CONNECTION_MILLIS =
+ 5 * DateUtils.SECOND_IN_MILLIS;
+
+ private final ComponentName mComponentName;
+
+ /**
+ * Callbacks to be invoked when the provider remote service responds with a
+ * success or failure.
+ * @param <T> the type of response expected from the provider
+ */
+ public interface ProviderCallbacks<T> {
+ /** Called when a successful response is received from the remote provider. */
+ void onProviderResponseSuccess(@Nullable T response);
+ /** Called when a failure response is received from the remote provider. */
+ void onProviderResponseFailure(int errorCode, @Nullable CharSequence message);
+ /** Called when the remote provider service dies. */
+ void onProviderServiceDied(RemoteCredentialService service);
+ }
+
+ public RemoteCredentialService(@NonNull Context context,
+ @NonNull ComponentName componentName, int userId) {
+ super(context, new Intent(CredentialProviderService.SERVICE_INTERFACE)
+ .setComponent(componentName), Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
+ userId, ICredentialProviderService.Stub::asInterface);
+ mComponentName = componentName;
+ }
+
+ /** Unbinds automatically after this amount of time. */
+ @Override
+ protected long getAutoDisconnectTimeoutMs() {
+ return TIMEOUT_IDLE_SERVICE_CONNECTION_MILLIS;
+ }
+
+ /** Return the componentName of the service to be connected. */
+ @NonNull public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ /** Destroys this remote service by unbinding the connection. */
+ public void destroy() {
+ unbind();
+ }
+
+ /** Main entry point to be called for executing a getCredential call on the remote
+ * provider service.
+ * @param request the request to be sent to the provider
+ * @param callback the callback to be used to send back the provider response to the
+ * {@link ProviderSession} class that maintains provider state
+ */
+ public void onGetCredentials(@NonNull GetCredentialsRequest request,
+ ProviderCallbacks<GetCredentialsResponse> callback) {
+ Log.i(TAG, "In onGetCredentials in RemoteCredentialService");
+ AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
+ AtomicReference<CompletableFuture<GetCredentialsResponse>> futureRef =
+ new AtomicReference<>();
+
+ CompletableFuture<GetCredentialsResponse> connectThenExecute = postAsync(service -> {
+ CompletableFuture<GetCredentialsResponse> getCredentials = new CompletableFuture<>();
+ ICancellationSignal cancellationSignal =
+ service.onGetCredentials(request, new IGetCredentialsCallback.Stub() {
+ @Override
+ public void onSuccess(GetCredentialsResponse response) {
+ Log.i(TAG, "In onSuccess in RemoteCredentialService");
+ getCredentials.complete(response);
+ }
+
+ @Override
+ public void onFailure(@CredentialProviderError int errorCode,
+ CharSequence message) {
+ Log.i(TAG, "In onFailure in RemoteCredentialService");
+ String errorMsg = message == null ? "" : String.valueOf(message);
+ getCredentials.completeExceptionally(new CredentialProviderException(
+ errorCode, errorMsg));
+ }
+ });
+ CompletableFuture<GetCredentialsResponse> future = futureRef.get();
+ if (future != null && future.isCancelled()) {
+ dispatchCancellationSignal(cancellationSignal);
+ } else {
+ cancellationSink.set(cancellationSignal);
+ }
+ return getCredentials;
+ }).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
+ futureRef.set(connectThenExecute);
+
+ connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() -> {
+ if (error == null) {
+ Log.i(TAG, "In RemoteCredentialService execute error is null");
+ callback.onProviderResponseSuccess(result);
+ } else {
+ if (error instanceof TimeoutException) {
+ Log.i(TAG, "In RemoteCredentialService execute error is timeout");
+ dispatchCancellationSignal(cancellationSink.get());
+ callback.onProviderResponseFailure(
+ CredentialProviderException.ERROR_TIMEOUT,
+ error.getMessage());
+ } else if (error instanceof CancellationException) {
+ Log.i(TAG, "In RemoteCredentialService execute error is cancellation");
+ dispatchCancellationSignal(cancellationSink.get());
+ callback.onProviderResponseFailure(
+ CredentialProviderException.ERROR_TASK_CANCELED,
+ error.getMessage());
+ } else if (error instanceof CredentialProviderException) {
+ Log.i(TAG, "In RemoteCredentialService execute error is provider error");
+ callback.onProviderResponseFailure(((CredentialProviderException) error)
+ .getErrorCode(),
+ error.getMessage());
+ } else {
+ Log.i(TAG, "In RemoteCredentialService execute error is unknown");
+ callback.onProviderResponseFailure(
+ CredentialProviderException.ERROR_UNKNOWN,
+ error.getMessage());
+ }
+ }
+ }));
+ }
+
+ private void dispatchCancellationSignal(@Nullable ICancellationSignal signal) {
+ if (signal == null) {
+ Slog.e(TAG, "Error dispatching a cancellation - Signal is null");
+ return;
+ }
+ try {
+ signal.cancel();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error dispatching a cancellation", e);
+ }
+ }
+}
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
new file mode 100644
index 0000000..1bacbb3
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 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.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.ComponentName;
+import android.content.Context;
+import android.credentials.ui.UserSelectionDialogResult;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+
+/**
+ * Base class of a request session, that listens to UI events. This class must be extended
+ * every time a new response type is expected from the providers.
+ */
+abstract class RequestSession implements CredentialManagerUi.CredentialManagerUiCallback,
+ ProviderSession.ProviderInternalCallback {
+ @NonNull protected final IBinder mRequestId;
+ @NonNull protected final Context mContext;
+ @NonNull protected final CredentialManagerUi mCredentialManagerUi;
+ @NonNull protected final String mRequestType;
+ @NonNull protected final Handler mHandler;
+ @NonNull protected boolean mIsFirstUiTurn = true;
+ @UserIdInt protected final int mUserId;
+
+ protected RequestSession(@NonNull Context context,
+ @UserIdInt int userId, @NonNull String requestType) {
+ mContext = context;
+ mUserId = userId;
+ mRequestType = requestType;
+ mHandler = new Handler(Looper.getMainLooper(), null, true);
+ mRequestId = new Binder();
+ mCredentialManagerUi = new CredentialManagerUi(mContext,
+ mUserId, this);
+ }
+
+ /** Returns the unique identifier of this request session. */
+ public IBinder getRequestId() {
+ return mRequestId;
+ }
+
+ @Override // from CredentialManagerUiCallback
+ public abstract void onUiSelection(UserSelectionDialogResult selection);
+
+ @Override // from CredentialManagerUiCallback
+ public abstract void onUiCancelation();
+
+ @Override // from ProviderInternalCallback
+ public abstract void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName);
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b74fedf..593e648 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -154,6 +154,7 @@
import com.android.server.people.PeopleService;
import com.android.server.pm.ApexManager;
import com.android.server.pm.ApexSystemServiceInfo;
+import com.android.server.pm.BackgroundInstallControlService;
import com.android.server.pm.CrossProfileAppsService;
import com.android.server.pm.DataLoaderManagerService;
import com.android.server.pm.DynamicCodeLoggingService;
@@ -2469,6 +2470,10 @@
t.traceBegin("StartMediaMetricsManager");
mSystemServiceManager.startService(MediaMetricsManagerService.class);
t.traceEnd();
+
+ t.traceBegin("StartBackgroundInstallControlService");
+ mSystemServiceManager.startService(BackgroundInstallControlService.class);
+ t.traceEnd();
}
t.traceBegin("StartMediaProjectionManager");
diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java
index 66524edf..35b9bc3 100644
--- a/services/print/java/com/android/server/print/PrintManagerService.java
+++ b/services/print/java/com/android/server/print/PrintManagerService.java
@@ -984,6 +984,7 @@
monitor.register(mContext, BackgroundThread.getHandler().getLooper(),
UserHandle.ALL, true);
}
+
private UserState getOrCreateUserStateLocked(int userId, boolean lowPriority) {
return getOrCreateUserStateLocked(userId, lowPriority,
true /* enforceUserUnlockingOrUnlocked */);
@@ -991,6 +992,12 @@
private UserState getOrCreateUserStateLocked(int userId, boolean lowPriority,
boolean enforceUserUnlockingOrUnlocked) {
+ return getOrCreateUserStateLocked(userId, lowPriority,
+ enforceUserUnlockingOrUnlocked, false /* shouldUpdateState */);
+ }
+
+ private UserState getOrCreateUserStateLocked(int userId, boolean lowPriority,
+ boolean enforceUserUnlockingOrUnlocked, boolean shouldUpdateState) {
if (enforceUserUnlockingOrUnlocked && !mUserManager.isUserUnlockingOrUnlocked(userId)) {
throw new IllegalStateException(
"User " + userId + " must be unlocked for printing to be available");
@@ -1000,6 +1007,8 @@
if (userState == null) {
userState = new UserState(mContext, userId, mLock, lowPriority);
mUserStates.put(userId, userState);
+ } else if (shouldUpdateState) {
+ userState.updateIfNeededLocked();
}
if (!lowPriority) {
@@ -1019,9 +1028,9 @@
UserState userState;
synchronized (mLock) {
- userState = getOrCreateUserStateLocked(userId, true,
- false /*enforceUserUnlockingOrUnlocked */);
- userState.updateIfNeededLocked();
+ userState = getOrCreateUserStateLocked(userId, /* lowPriority */ true,
+ /* enforceUserUnlockingOrUnlocked */ false,
+ /* shouldUpdateState */ true);
}
// This is the first time we switch to this user after boot, so
// now is the time to remove obsolete print jobs since they
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
index f46877e..2547347 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
@@ -16,10 +16,19 @@
package com.android.server.job;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.job.JobConcurrencyManager.KEY_PKG_CONCURRENCY_LIMIT_EJ;
import static com.android.server.job.JobConcurrencyManager.KEY_PKG_CONCURRENCY_LIMIT_REGULAR;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER_IMPORTANT;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_EJ;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_FGS;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -47,15 +56,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG;
-import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER;
-import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER_IMPORTANT;
-import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_EJ;
-import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_FGS;
-import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
-import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
-
import com.android.internal.R;
import com.android.internal.app.IBatteryStats;
import com.android.server.LocalServices;
@@ -73,6 +73,7 @@
import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.List;
@@ -124,6 +125,7 @@
mMockingSession = mockitoSession()
.initMocks(this)
.mockStatic(AppGlobals.class)
+ .spyStatic(DeviceConfig.class)
.strictness(Strictness.LENIENT)
.startMocking();
final JobSchedulerService jobSchedulerService = mock(JobSchedulerService.class);
@@ -134,6 +136,8 @@
when(mContext.getResources()).thenReturn(mResources);
doReturn(mContext).when(jobSchedulerService).getTestableContext();
mConfigBuilder = new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
+ doAnswer((Answer<DeviceConfig.Properties>) invocationOnMock -> mConfigBuilder.build())
+ .when(() -> DeviceConfig.getProperties(eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER)));
mPendingJobQueue = new PendingJobQueue();
doReturn(mPendingJobQueue).when(jobSchedulerService).getPendingJobQueue();
doReturn(mIPackageManager).when(AppGlobals::getPackageManager);
@@ -595,7 +599,6 @@
}
private void updateDeviceConfig() throws Exception {
- DeviceConfig.setProperties(mConfigBuilder.build());
mJobConcurrencyManager.updateConfigLocked();
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
index aa95916..f88e18b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
@@ -184,8 +184,10 @@
when(mJobSchedulerService.isCurrentlyRunningLocked(jobLowPriorityRunning)).thenReturn(true);
when(mJobSchedulerService.isCurrentlyRunningLocked(jobHighPriorityRunning))
.thenReturn(true);
- when(mJobSchedulerService.isLongRunningLocked(jobLowPriorityRunningLong)).thenReturn(true);
- when(mJobSchedulerService.isLongRunningLocked(jobHighPriorityRunningLong)).thenReturn(true);
+ when(mJobSchedulerService.isJobInOvertimeLocked(jobLowPriorityRunningLong))
+ .thenReturn(true);
+ when(mJobSchedulerService.isJobInOvertimeLocked(jobHighPriorityRunningLong))
+ .thenReturn(true);
assertFalse(mThermalStatusRestriction.isJobRestricted(jobMinPriority));
assertFalse(mThermalStatusRestriction.isJobRestricted(jobLowPriority));
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java
deleted file mode 100644
index 278e04a..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2022 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.pm;
-
-import static android.os.UserHandle.USER_SYSTEM;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.INVALID_DISPLAY;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assert.assertThrows;
-
-import android.util.Log;
-
-import org.junit.Test;
-
-/**
- * Run as {@code atest FrameworksMockingServicesTests:com.android.server.pm.UserManagerInternalTest}
- */
-public final class UserManagerInternalTest extends UserManagerServiceOrInternalTestCase {
-
- private static final String TAG = UserManagerInternalTest.class.getSimpleName();
-
- // NOTE: most the tests below only apply to MUMD configurations, so we're not adding _mumd_
- // in the test names, but _nonMumd_ instead
-
- @Test
- public void testAssignUserToDisplay_nonMumd_defaultDisplayIgnored() {
- mUmi.assignUserToDisplay(USER_ID, DEFAULT_DISPLAY);
-
- assertNoUserAssignedToDisplay();
- }
-
- @Test
- public void testAssignUserToDisplay_nonMumd_otherDisplay_currentUser() {
- mockCurrentUser(USER_ID);
-
- assertThrows(UnsupportedOperationException.class,
- () -> mUmi.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID));
- }
-
- @Test
- public void testAssignUserToDisplay_nonMumd_otherDisplay_startProfileOfcurrentUser() {
- mockCurrentUser(PARENT_USER_ID);
- addDefaultProfileAndParent();
- startDefaultProfile();
-
- assertThrows(UnsupportedOperationException.class,
- () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
- }
-
- @Test
- public void testAssignUserToDisplay_nonMumd_otherDisplay_stoppedProfileOfcurrentUser() {
- mockCurrentUser(PARENT_USER_ID);
- addDefaultProfileAndParent();
- stopDefaultProfile();
-
- assertThrows(UnsupportedOperationException.class,
- () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
- }
-
- @Test
- public void testAssignUserToDisplay_defaultDisplayIgnored() {
- enableUsersOnSecondaryDisplays();
-
- mUmi.assignUserToDisplay(USER_ID, DEFAULT_DISPLAY);
-
- assertNoUserAssignedToDisplay();
- }
-
- @Test
- public void testAssignUserToDisplay_systemUser() {
- enableUsersOnSecondaryDisplays();
-
- assertThrows(IllegalArgumentException.class,
- () -> mUmi.assignUserToDisplay(USER_SYSTEM, SECONDARY_DISPLAY_ID));
- }
-
- @Test
- public void testAssignUserToDisplay_invalidDisplay() {
- enableUsersOnSecondaryDisplays();
-
- assertThrows(IllegalArgumentException.class,
- () -> mUmi.assignUserToDisplay(USER_ID, INVALID_DISPLAY));
- }
-
- @Test
- public void testAssignUserToDisplay_currentUser() {
- enableUsersOnSecondaryDisplays();
- mockCurrentUser(USER_ID);
-
- assertThrows(IllegalArgumentException.class,
- () -> mUmi.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID));
-
- assertNoUserAssignedToDisplay();
- }
-
- @Test
- public void testAssignUserToDisplay_startedProfileOfCurrentUser() {
- enableUsersOnSecondaryDisplays();
- mockCurrentUser(PARENT_USER_ID);
- addDefaultProfileAndParent();
- startDefaultProfile();
-
- IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
- () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
-
- Log.v(TAG, "Exception: " + e);
- assertNoUserAssignedToDisplay();
- }
-
- @Test
- public void testAssignUserToDisplay_stoppedProfileOfCurrentUser() {
- enableUsersOnSecondaryDisplays();
- mockCurrentUser(PARENT_USER_ID);
- addDefaultProfileAndParent();
- stopDefaultProfile();
-
- IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
- () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
-
- Log.v(TAG, "Exception: " + e);
- assertNoUserAssignedToDisplay();
- }
-
- @Test
- public void testAssignUserToDisplay_displayAvailable() {
- enableUsersOnSecondaryDisplays();
-
- mUmi.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
- assertUserAssignedToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
- }
-
- @Test
- public void testAssignUserToDisplay_displayAlreadyAssigned() {
- enableUsersOnSecondaryDisplays();
-
- mUmi.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
- IllegalStateException e = assertThrows(IllegalStateException.class,
- () -> mUmi.assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID));
-
- Log.v(TAG, "Exception: " + e);
- assertWithMessage("exception (%s) message", e).that(e).hasMessageThat()
- .matches("Cannot.*" + OTHER_USER_ID + ".*" + SECONDARY_DISPLAY_ID + ".*already.*"
- + USER_ID + ".*");
- }
-
- @Test
- public void testAssignUserToDisplay_userAlreadyAssigned() {
- enableUsersOnSecondaryDisplays();
-
- mUmi.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
- IllegalStateException e = assertThrows(IllegalStateException.class,
- () -> mUmi.assignUserToDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID));
-
- Log.v(TAG, "Exception: " + e);
- assertWithMessage("exception (%s) message", e).that(e).hasMessageThat()
- .matches("Cannot.*" + USER_ID + ".*" + OTHER_SECONDARY_DISPLAY_ID + ".*already.*"
- + SECONDARY_DISPLAY_ID + ".*");
-
- assertUserAssignedToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
- }
-
- @Test
- public void testAssignUserToDisplay_profileOnSameDisplayAsParent() {
- enableUsersOnSecondaryDisplays();
- addDefaultProfileAndParent();
-
- mUmi.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
- IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
- () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
-
- Log.v(TAG, "Exception: " + e);
- assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
- }
-
- @Test
- public void testAssignUserToDisplay_profileOnDifferentDisplayAsParent() {
- enableUsersOnSecondaryDisplays();
- addDefaultProfileAndParent();
-
- mUmi.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
- IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
- () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, OTHER_SECONDARY_DISPLAY_ID));
-
- Log.v(TAG, "Exception: " + e);
- assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
- }
-
- @Test
- public void testAssignUserToDisplay_profileDefaultDisplayParentOnSecondaryDisplay() {
- enableUsersOnSecondaryDisplays();
- addDefaultProfileAndParent();
-
- mUmi.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
- IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
- () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY));
-
- Log.v(TAG, "Exception: " + e);
- assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
- }
-
- @Test
- public void testUnassignUserFromDisplay_nonMumd_ignored() {
- mockCurrentUser(USER_ID);
-
- mUmi.unassignUserFromDisplay(USER_SYSTEM);
- mUmi.unassignUserFromDisplay(USER_ID);
- mUmi.unassignUserFromDisplay(OTHER_USER_ID);
-
- assertNoUserAssignedToDisplay();
- }
-
- @Test
- public void testUnassignUserFromDisplay() {
- testAssignUserToDisplay_displayAvailable();
-
- mUmi.unassignUserFromDisplay(USER_ID);
-
- assertNoUserAssignedToDisplay();
- }
-
- @Override
- protected boolean isUserVisible(int userId) {
- return mUmi.isUserVisible(userId);
- }
-
- @Override
- protected boolean isUserVisibleOnDisplay(int userId, int displayId) {
- return mUmi.isUserVisible(userId, displayId);
- }
-
- @Override
- protected int getDisplayAssignedToUser(int userId) {
- return mUmi.getDisplayAssignedToUser(userId);
- }
-
- @Override
- protected int getUserAssignedToDisplay(int displayId) {
- return mUmi.getUserAssignedToDisplay(displayId);
- }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java
index 90a5fa0..6c85b7a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java
@@ -15,10 +15,6 @@
*/
package com.android.server.pm;
-import static android.os.UserHandle.USER_NULL;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.INVALID_DISPLAY;
-
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -34,7 +30,6 @@
import android.os.UserManager;
import android.util.Log;
import android.util.SparseArray;
-import android.util.SparseIntArray;
import androidx.test.annotation.UiThreadTest;
@@ -46,12 +41,8 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Test;
import org.mockito.Mock;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
/**
* Base class for {@link UserManagerInternalTest} and {@link UserManagerInternalTest}.
*
@@ -59,9 +50,13 @@
* "symbiotic relationship - some methods of the former simply call the latter and vice versa.
*
* <p>Ideally, only one of them should have the logic, but since that's not the case, this class
- * provices the infra to make it easier to test both (which in turn would make it easier / safer to
+ * provides the infra to make it easier to test both (which in turn would make it easier / safer to
* refactor their logic later).
*/
+// TODO(b/244644281): there is no UserManagerInternalTest anymore as the logic being tested there
+// moved to UserVisibilityController, so it might be simpler to merge this class into
+// UserManagerServiceTest (once the UserVisibilityController -> UserManagerService dependency is
+// fixed)
abstract class UserManagerServiceOrInternalTestCase extends ExtendedMockitoTestCase {
private static final String TAG = UserManagerServiceOrInternalTestCase.class.getSimpleName();
@@ -90,31 +85,12 @@
*/
protected static final int PROFILE_USER_ID = 643;
- /**
- * Id of a secondary display (i.e, not {@link android.view.Display.DEFAULT_DISPLAY}).
- */
- protected static final int SECONDARY_DISPLAY_ID = 42;
-
- /**
- * Id of another secondary display (i.e, not {@link android.view.Display.DEFAULT_DISPLAY}).
- */
- protected static final int OTHER_SECONDARY_DISPLAY_ID = 108;
-
private final Object mPackagesLock = new Object();
private final Context mRealContext = androidx.test.InstrumentationRegistry.getInstrumentation()
.getTargetContext();
private final SparseArray<UserData> mUsers = new SparseArray<>();
- // TODO(b/244644281): manipulating mUsersOnSecondaryDisplays directly leaks implementation
- // details into the unit test, but it's fine for now - in the long term, this logic should be
- // refactored into a proper UserDisplayAssignment class.
- private final SparseIntArray mUsersOnSecondaryDisplays = new SparseIntArray();
-
private Context mSpiedContext;
- private UserManagerService mStandardUms;
- private UserManagerService mMumdUms;
- private UserManagerInternal mStandardUmi;
- private UserManagerInternal mMumdUmi;
private @Mock PackageManagerService mMockPms;
private @Mock UserDataPreparer mMockUserDataPreparer;
@@ -122,17 +98,11 @@
/**
* Reference to the {@link UserManagerService} being tested.
- *
- * <p>By default, such service doesn't support {@code MUMD} (Multiple Users on Multiple
- * Displays), but that can be changed by calling {@link #enableUsersOnSecondaryDisplays()}.
*/
protected UserManagerService mUms;
/**
* Reference to the {@link UserManagerInternal} being tested.
- *
- * <p>By default, such service doesn't support {@code MUMD} (Multiple Users on Multiple
- * Displays), but that can be changed by calling {@link #enableUsersOnSecondaryDisplays()}.
*/
protected UserManagerInternal mUmi;
@@ -151,32 +121,12 @@
// Called when WatchedUserStates is constructed
doNothing().when(() -> UserManager.invalidateIsUserUnlockedCache());
- // Need to set both UserManagerService instances here, as they need to be run in the
- // UiThread
-
- // mMumdUms / mMumdUmi
- mockIsUsersOnSecondaryDisplaysEnabled(/* usersOnSecondaryDisplaysEnabled= */ true);
- mMumdUms = new UserManagerService(mSpiedContext, mMockPms, mMockUserDataPreparer,
- mPackagesLock, mRealContext.getDataDir(), mUsers, mUsersOnSecondaryDisplays);
- assertWithMessage("UserManagerService.isUsersOnSecondaryDisplaysEnabled()")
- .that(mMumdUms.isUsersOnSecondaryDisplaysEnabled())
- .isTrue();
- mMumdUmi = LocalServices.getService(UserManagerInternal.class);
- assertWithMessage("LocalServices.getService(UserManagerInternal.class)").that(mMumdUmi)
+ // Must construct UserManagerService in the UiThread
+ mUms = new UserManagerService(mSpiedContext, mMockPms, mMockUserDataPreparer,
+ mPackagesLock, mRealContext.getDataDir(), mUsers);
+ mUmi = LocalServices.getService(UserManagerInternal.class);
+ assertWithMessage("LocalServices.getService(UserManagerInternal.class)").that(mUmi)
.isNotNull();
- resetUserManagerInternal();
-
- // mStandardUms / mStandardUmi
- mockIsUsersOnSecondaryDisplaysEnabled(/* usersOnSecondaryDisplaysEnabled= */ false);
- mStandardUms = new UserManagerService(mSpiedContext, mMockPms, mMockUserDataPreparer,
- mPackagesLock, mRealContext.getDataDir(), mUsers, mUsersOnSecondaryDisplays);
- assertWithMessage("UserManagerService.isUsersOnSecondaryDisplaysEnabled()")
- .that(mStandardUms.isUsersOnSecondaryDisplaysEnabled())
- .isFalse();
- mStandardUmi = LocalServices.getService(UserManagerInternal.class);
- assertWithMessage("LocalServices.getService(UserManagerInternal.class)").that(mStandardUmi)
- .isNotNull();
- setServiceFixtures(/*usersOnSecondaryDisplaysEnabled= */ false);
}
@After
@@ -185,373 +135,10 @@
LocalServices.removeServiceForTest(UserManagerInternal.class);
}
- //////////////////////////////////////////////////////////////////////////////////////////////
- // Methods whose UMS implementation calls UMI or vice-versa - they're tested in this class, //
- // but the subclass must provide the proper implementation //
- //////////////////////////////////////////////////////////////////////////////////////////////
-
- protected abstract boolean isUserVisible(int userId);
- protected abstract boolean isUserVisibleOnDisplay(int userId, int displayId);
- protected abstract int getDisplayAssignedToUser(int userId);
- protected abstract int getUserAssignedToDisplay(int displayId);
-
- /////////////////////////////////
- // Tests for the above methods //
- /////////////////////////////////
-
- @Test
- public void testIsUserVisible_invalidUser() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("isUserVisible(%s)", USER_NULL).that(isUserVisible(USER_NULL)).isFalse();
- }
-
- @Test
- public void testIsUserVisible_currentUser() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("isUserVisible(%s)", USER_ID).that(isUserVisible(USER_ID)).isTrue();
- }
-
- @Test
- public void testIsUserVisible_nonCurrentUser() {
- mockCurrentUser(OTHER_USER_ID);
-
- assertWithMessage("isUserVisible(%s)", USER_ID).that(isUserVisible(USER_ID)).isFalse();
- }
-
- @Test
- public void testIsUserVisible_startedProfileOfcurrentUser() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- startDefaultProfile();
- setUserState(PROFILE_USER_ID, UserState.STATE_RUNNING_UNLOCKED);
-
- assertWithMessage("isUserVisible(%s)", PROFILE_USER_ID).that(isUserVisible(PROFILE_USER_ID))
- .isTrue();
- }
-
- @Test
- public void testIsUserVisible_stoppedProfileOfcurrentUser() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- stopDefaultProfile();
-
- assertWithMessage("isUserVisible(%s)", PROFILE_USER_ID).that(isUserVisible(PROFILE_USER_ID))
- .isFalse();
- }
-
- @Test
- public void testIsUserVisible_bgUserOnSecondaryDisplay() {
- enableUsersOnSecondaryDisplays();
- mockCurrentUser(OTHER_USER_ID);
- assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
- assertWithMessage("isUserVisible(%s)", USER_ID).that(isUserVisible(USER_ID)).isTrue();
- }
-
- // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
- // isUserVisible() for bg users relies only on the user / display assignments
-
- @Test
- public void testIsUserVisibleOnDisplay_invalidUser() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", USER_NULL, DEFAULT_DISPLAY)
- .that(isUserVisibleOnDisplay(USER_NULL, DEFAULT_DISPLAY)).isFalse();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_currentUserInvalidDisplay() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", USER_ID, INVALID_DISPLAY)
- .that(isUserVisibleOnDisplay(USER_ID, INVALID_DISPLAY)).isFalse();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_currentUserDefaultDisplay() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", USER_ID, DEFAULT_DISPLAY)
- .that(isUserVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY)).isTrue();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_currentUserSecondaryDisplay() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
- .that(isUserVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_mumd_currentUserUnassignedSecondaryDisplay() {
- enableUsersOnSecondaryDisplays();
- mockCurrentUser(USER_ID);
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
- .that(isUserVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_mumd_currentUserSecondaryDisplayAssignedToAnotherUser() {
- enableUsersOnSecondaryDisplays();
- mockCurrentUser(USER_ID);
- assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
- .that(isUserVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_mumd_startedProfileOfCurrentUserSecondaryDisplayAssignedToAnotherUser() {
- enableUsersOnSecondaryDisplays();
- addDefaultProfileAndParent();
- startDefaultProfile();
- mockCurrentUser(PARENT_USER_ID);
- assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
- .that(isUserVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_mumd_stoppedProfileOfCurrentUserSecondaryDisplayAssignedToAnotherUser() {
- enableUsersOnSecondaryDisplays();
- addDefaultProfileAndParent();
- stopDefaultProfile();
- mockCurrentUser(PARENT_USER_ID);
- assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
- .that(isUserVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_nonCurrentUserDefaultDisplay() {
- mockCurrentUser(OTHER_USER_ID);
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", USER_ID, DEFAULT_DISPLAY)
- .that(isUserVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY)).isFalse();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_startedProfileOfcurrentUserInvalidDisplay() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- startDefaultProfile();
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", PROFILE_USER_ID, INVALID_DISPLAY)
- .that(isUserVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY)).isTrue();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserInvalidDisplay() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- stopDefaultProfile();
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", PROFILE_USER_ID, INVALID_DISPLAY)
- .that(isUserVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY)).isFalse();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_startedProfileOfcurrentUserDefaultDisplay() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- startDefaultProfile();
- setUserState(PROFILE_USER_ID, UserState.STATE_RUNNING_UNLOCKED);
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", PROFILE_USER_ID, DEFAULT_DISPLAY)
- .that(isUserVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY)).isTrue();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserDefaultDisplay() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- stopDefaultProfile();
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", PROFILE_USER_ID, DEFAULT_DISPLAY)
- .that(isUserVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY)).isFalse();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_startedProfileOfcurrentUserSecondaryDisplay() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- startDefaultProfile();
- setUserState(PROFILE_USER_ID, UserState.STATE_RUNNING_UNLOCKED);
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
- .that(isUserVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserSecondaryDisplay() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- stopDefaultProfile();
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
- .that(isUserVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_mumd_bgUserOnSecondaryDisplay() {
- enableUsersOnSecondaryDisplays();
- mockCurrentUser(OTHER_USER_ID);
- assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
- .that(isUserVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_mumd_bgUserOnAnotherSecondaryDisplay() {
- enableUsersOnSecondaryDisplays();
- mockCurrentUser(OTHER_USER_ID);
- assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
- .that(isUserVisibleOnDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID)).isFalse();
- }
-
- // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
- // isUserVisibleOnDisplay() for bg users relies only on the user / display assignments
-
- @Test
- public void testGetDisplayAssignedToUser_invalidUser() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("getDisplayAssignedToUser(%s)", USER_NULL)
- .that(getDisplayAssignedToUser(USER_NULL)).isEqualTo(INVALID_DISPLAY);
- }
-
- @Test
- public void testGetDisplayAssignedToUser_currentUser() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("getDisplayAssignedToUser(%s)", USER_ID)
- .that(getDisplayAssignedToUser(USER_ID)).isEqualTo(DEFAULT_DISPLAY);
- }
-
- @Test
- public void testGetDisplayAssignedToUser_nonCurrentUser() {
- mockCurrentUser(OTHER_USER_ID);
-
- assertWithMessage("getDisplayAssignedToUser(%s)", USER_ID)
- .that(getDisplayAssignedToUser(USER_ID)).isEqualTo(INVALID_DISPLAY);
- }
-
- @Test
- public void testGetDisplayAssignedToUser_startedProfileOfcurrentUser() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- startDefaultProfile();
- setUserState(PROFILE_USER_ID, UserState.STATE_RUNNING_UNLOCKED);
-
- assertWithMessage("getDisplayAssignedToUser(%s)", PROFILE_USER_ID)
- .that(getDisplayAssignedToUser(PROFILE_USER_ID)).isEqualTo(DEFAULT_DISPLAY);
- }
-
- @Test
- public void testGetDisplayAssignedToUser_stoppedProfileOfcurrentUser() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- stopDefaultProfile();
-
- assertWithMessage("getDisplayAssignedToUser(%s)", PROFILE_USER_ID)
- .that(getDisplayAssignedToUser(PROFILE_USER_ID)).isEqualTo(INVALID_DISPLAY);
- }
-
- @Test
- public void testGetDisplayAssignedToUser_bgUserOnSecondaryDisplay() {
- enableUsersOnSecondaryDisplays();
- mockCurrentUser(OTHER_USER_ID);
- assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
- assertWithMessage("getDisplayAssignedToUser(%s)", USER_ID)
- .that(getDisplayAssignedToUser(USER_ID)).isEqualTo(SECONDARY_DISPLAY_ID);
- }
-
- // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
- // getDisplayAssignedToUser() for bg users relies only on the user / display assignments
-
- @Test
- public void testGetUserAssignedToDisplay_invalidDisplay() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("getUserAssignedToDisplay(%s)", INVALID_DISPLAY)
- .that(getUserAssignedToDisplay(INVALID_DISPLAY)).isEqualTo(USER_ID);
- }
-
- @Test
- public void testGetUserAssignedToDisplay_defaultDisplay() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("getUserAssignedToDisplay(%s)", DEFAULT_DISPLAY)
- .that(getUserAssignedToDisplay(DEFAULT_DISPLAY)).isEqualTo(USER_ID);
- }
-
- @Test
- public void testGetUserAssignedToDisplay_secondaryDisplay() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
- .that(getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID);
- }
-
- @Test
- public void testGetUserAssignedToDisplay_mumd_bgUserOnSecondaryDisplay() {
- enableUsersOnSecondaryDisplays();
- mockCurrentUser(OTHER_USER_ID);
- assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
- assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
- .that(getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID);
- }
-
- @Test
- public void testGetUserAssignedToDisplay_mumd_noUserOnSecondaryDisplay() {
- enableUsersOnSecondaryDisplays();
- mockCurrentUser(USER_ID);
-
- assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
- .that(getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID);
- }
-
- // TODO(b/244644281): scenario below shouldn't happen on "real life", as the profile cannot be
- // started on secondary display if its parent isn't, so we might need to remove (or refactor
- // this test) if/when the underlying logic changes
- @Test
- public void testGetUserAssignedToDisplay_mumd_profileOnSecondaryDisplay() {
- enableUsersOnSecondaryDisplays();
- addDefaultProfileAndParent();
- mockCurrentUser(USER_ID);
- assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
-
- assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
- .that(getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID);
- }
-
- // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
- // getUserAssignedToDisplay() for bg users relies only on the user / display assignments
-
-
///////////////////////////////////////////
// Helper methods exposed to sub-classes //
///////////////////////////////////////////
- /**
- * Change test fixtures to use a version that supports {@code MUMD} (Multiple Users on Multiple
- * Displays).
- */
- protected final void enableUsersOnSecondaryDisplays() {
- setServiceFixtures(/* usersOnSecondaryDisplaysEnabled= */ true);
- }
-
protected final void mockCurrentUser(@UserIdInt int userId) {
mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal);
@@ -597,65 +184,19 @@
setUserState(userId, UserState.STATE_STOPPING);
}
- // NOTE: should only called by tests that indirectly needs to check user assignments (like
- // isUserVisible), not by tests for the user assignment methods per se.
- protected final void assignUserToDisplay(@UserIdInt int userId, int displayId) {
- mUsersOnSecondaryDisplays.put(userId, displayId);
- }
-
- protected final void assertNoUserAssignedToDisplay() {
- assertWithMessage("mUsersOnSecondaryDisplays()").that(usersOnSecondaryDisplaysAsMap())
- .isEmpty();
- }
-
- protected final void assertUserAssignedToDisplay(@UserIdInt int userId, int displayId) {
- assertWithMessage("mUsersOnSecondaryDisplays()").that(usersOnSecondaryDisplaysAsMap())
- .containsExactly(userId, displayId);
+ protected final void setUserState(@UserIdInt int userId, int userState) {
+ mUmi.setUserState(userId, userState);
}
///////////////////
// Private infra //
///////////////////
- private void setServiceFixtures(boolean usersOnSecondaryDisplaysEnabled) {
- Log.d(TAG, "Setting fixtures for usersOnSecondaryDisplaysEnabled="
- + usersOnSecondaryDisplaysEnabled);
- if (usersOnSecondaryDisplaysEnabled) {
- mUms = mMumdUms;
- mUmi = mMumdUmi;
- } else {
- mUms = mStandardUms;
- mUmi = mStandardUmi;
- }
- }
-
- private void mockIsUsersOnSecondaryDisplaysEnabled(boolean enabled) {
- Log.d(TAG, "Mocking UserManager.isUsersOnSecondaryDisplaysEnabled() to return " + enabled);
- doReturn(enabled).when(() -> UserManager.isUsersOnSecondaryDisplaysEnabled());
- }
-
private void addUserData(TestUserData userData) {
Log.d(TAG, "Adding " + userData);
mUsers.put(userData.info.id, userData);
}
- private void setUserState(@UserIdInt int userId, int userState) {
- mUmi.setUserState(userId, userState);
- }
-
- private void removeUserState(@UserIdInt int userId) {
- mUmi.removeUserState(userId);
- }
-
- private Map<Integer, Integer> usersOnSecondaryDisplaysAsMap() {
- int size = mUsersOnSecondaryDisplays.size();
- Map<Integer, Integer> map = new LinkedHashMap<>(size);
- for (int i = 0; i < size; i++) {
- map.put(mUsersOnSecondaryDisplays.keyAt(i), mUsersOnSecondaryDisplays.valueAt(i));
- }
- return map;
- }
-
private static final class TestUserData extends UserData {
@SuppressWarnings("deprecation")
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 8b5921c..b5ffe5f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -123,24 +123,4 @@
assertWithMessage("isUserRunning(%s)", PROFILE_USER_ID)
.that(mUms.isUserRunning(PROFILE_USER_ID)).isFalse();
}
-
- @Override
- protected boolean isUserVisible(int userId) {
- return mUms.isUserVisibleUnchecked(userId);
- }
-
- @Override
- protected boolean isUserVisibleOnDisplay(int userId, int displayId) {
- return mUms.isUserVisibleOnDisplay(userId, displayId);
- }
-
- @Override
- protected int getDisplayAssignedToUser(int userId) {
- return mUms.getDisplayAssignedToUser(userId);
- }
-
- @Override
- protected int getUserAssignedToDisplay(int displayId) {
- return mUms.getUserAssignedToDisplay(displayId);
- }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
new file mode 100644
index 0000000..21f541f
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2022 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.pm;
+
+import static android.os.UserHandle.USER_SYSTEM;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+
+import android.util.Log;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@link UserVisibilityMediator} tests for devices that support concurrent Multiple
+ * Users on Multiple Displays (A.K.A {@code MUMD}).
+ *
+ * <p>Run as
+ * {@code atest FrameworksMockingServicesTests:com.android.server.pm.UserVisibilityMediatorMUMDTest}
+ */
+public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediatorTestCase {
+
+ private static final String TAG = UserVisibilityMediatorMUMDTest.class.getSimpleName();
+
+ public UserVisibilityMediatorMUMDTest() {
+ super(/* usersOnSecondaryDisplaysEnabled= */ true);
+ }
+
+ @Test
+ public void testAssignUserToDisplay_systemUser() {
+ assertThrows(IllegalArgumentException.class,
+ () -> mMediator.assignUserToDisplay(USER_SYSTEM, SECONDARY_DISPLAY_ID));
+ }
+
+ @Test
+ public void testAssignUserToDisplay_invalidDisplay() {
+ assertThrows(IllegalArgumentException.class,
+ () -> mMediator.assignUserToDisplay(USER_ID, INVALID_DISPLAY));
+ }
+
+ @Test
+ public void testAssignUserToDisplay_currentUser() {
+ mockCurrentUser(USER_ID);
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID));
+
+ assertNoUserAssignedToDisplay();
+ }
+
+ @Test
+ public void testAssignUserToDisplay_startedProfileOfCurrentUser() {
+ mockCurrentUser(PARENT_USER_ID);
+ addDefaultProfileAndParent();
+ startDefaultProfile();
+
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+
+ Log.v(TAG, "Exception: " + e);
+ assertNoUserAssignedToDisplay();
+ }
+
+ @Test
+ public void testAssignUserToDisplay_stoppedProfileOfCurrentUser() {
+ mockCurrentUser(PARENT_USER_ID);
+ addDefaultProfileAndParent();
+ stopDefaultProfile();
+
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+
+ Log.v(TAG, "Exception: " + e);
+ assertNoUserAssignedToDisplay();
+ }
+
+ @Test
+ public void testAssignUserToDisplay_displayAvailable() {
+ mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+
+ assertUserAssignedToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+ }
+
+ @Test
+ public void testAssignUserToDisplay_displayAlreadyAssigned() {
+ mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+
+ IllegalStateException e = assertThrows(IllegalStateException.class,
+ () -> mMediator.assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID));
+
+ Log.v(TAG, "Exception: " + e);
+ assertWithMessage("exception (%s) message", e).that(e).hasMessageThat()
+ .matches("Cannot.*" + OTHER_USER_ID + ".*" + SECONDARY_DISPLAY_ID + ".*already.*"
+ + USER_ID + ".*");
+ }
+
+ @Test
+ public void testAssignUserToDisplay_userAlreadyAssigned() {
+ mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+
+ IllegalStateException e = assertThrows(IllegalStateException.class,
+ () -> mMediator.assignUserToDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID));
+
+ Log.v(TAG, "Exception: " + e);
+ assertWithMessage("exception (%s) message", e).that(e).hasMessageThat()
+ .matches("Cannot.*" + USER_ID + ".*" + OTHER_SECONDARY_DISPLAY_ID + ".*already.*"
+ + SECONDARY_DISPLAY_ID + ".*");
+
+ assertUserAssignedToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+ }
+
+ @Test
+ public void testAssignUserToDisplay_profileOnSameDisplayAsParent() {
+ addDefaultProfileAndParent();
+
+ mMediator.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+
+ Log.v(TAG, "Exception: " + e);
+ assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+ }
+
+ @Test
+ public void testAssignUserToDisplay_profileOnDifferentDisplayAsParent() {
+ addDefaultProfileAndParent();
+
+ mMediator.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, OTHER_SECONDARY_DISPLAY_ID));
+
+ Log.v(TAG, "Exception: " + e);
+ assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+ }
+
+ @Test
+ public void testAssignUserToDisplay_profileDefaultDisplayParentOnSecondaryDisplay() {
+ addDefaultProfileAndParent();
+
+ mMediator.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY));
+
+ Log.v(TAG, "Exception: " + e);
+ assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+ }
+
+ @Test
+ public void testUnassignUserFromDisplay() {
+ testAssignUserToDisplay_displayAvailable();
+
+ mMediator.unassignUserFromDisplay(USER_ID);
+
+ assertNoUserAssignedToDisplay();
+ }
+
+ @Test
+ public void testIsUserVisible_bgUserOnSecondaryDisplay() {
+ mockCurrentUser(OTHER_USER_ID);
+ assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+
+ assertWithMessage("isUserVisible(%s)", USER_ID)
+ .that(mMediator.isUserVisible(USER_ID)).isTrue();
+ }
+
+ // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
+ // isUserVisible() for bg users relies only on the user / display assignments
+
+ @Test
+ public void testIsUserVisibleOnDisplay_currentUserUnassignedSecondaryDisplay() {
+ mockCurrentUser(USER_ID);
+
+ assertWithMessage("isUserVisible(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
+ .that(mMediator.isUserVisible(USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
+ }
+
+ @Test
+ public void testIsUserVisibleOnDisplay_currentUserSecondaryDisplayAssignedToAnotherUser() {
+ mockCurrentUser(USER_ID);
+ assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
+
+ assertWithMessage("isUserVisible(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
+ .that(mMediator.isUserVisible(USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
+ }
+
+ @Test
+ public void testIsUserVisibleOnDisplay_startedProfileOfCurrentUserSecondaryDisplayAssignedToAnotherUser() {
+ addDefaultProfileAndParent();
+ startDefaultProfile();
+ mockCurrentUser(PARENT_USER_ID);
+ assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
+
+ assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
+ .that(mMediator.isUserVisible(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
+ }
+
+ @Test
+ public void testIsUserVisibleOnDisplay_stoppedProfileOfCurrentUserSecondaryDisplayAssignedToAnotherUser() {
+ addDefaultProfileAndParent();
+ stopDefaultProfile();
+ mockCurrentUser(PARENT_USER_ID);
+ assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
+
+ assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
+ .that(mMediator.isUserVisible(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
+ }
+
+ @Test
+ public void testIsUserVisibleOnDisplay_startedProfileOfCurrentUserOnUnassignedSecondaryDisplay() {
+ addDefaultProfileAndParent();
+ startDefaultProfile();
+ mockCurrentUser(PARENT_USER_ID);
+
+ // TODO(b/244644281): change it to isFalse() once isUserVisible() is fixed (see note there)
+ assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
+ .that(mMediator.isUserVisible(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
+ }
+
+ @Test
+ public void testIsUserVisibleOnDisplay_bgUserOnSecondaryDisplay() {
+ mockCurrentUser(OTHER_USER_ID);
+ assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+
+ assertWithMessage("isUserVisible(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
+ .that(mMediator.isUserVisible(USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
+ }
+
+ @Test
+ public void testIsUserVisibleOnDisplay_bgUserOnAnotherSecondaryDisplay() {
+ mockCurrentUser(OTHER_USER_ID);
+ assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+
+ assertWithMessage("isUserVisible(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
+ .that(mMediator.isUserVisible(USER_ID, OTHER_SECONDARY_DISPLAY_ID)).isFalse();
+ }
+
+ // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
+ // the tests for isUserVisible(userId, display) for non-current users relies on the explicit
+ // user / display assignments
+ // TODO(b/244644281): add such tests if the logic change
+
+ @Test
+ public void testGetDisplayAssignedToUser_bgUserOnSecondaryDisplay() {
+ mockCurrentUser(OTHER_USER_ID);
+ assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+
+ assertWithMessage("getDisplayAssignedToUser(%s)", USER_ID)
+ .that(mMediator.getDisplayAssignedToUser(USER_ID))
+ .isEqualTo(SECONDARY_DISPLAY_ID);
+ }
+
+ // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
+ // getDisplayAssignedToUser() for bg users relies only on the user / display assignments
+
+ @Test
+ public void testGetUserAssignedToDisplay_bgUserOnSecondaryDisplay() {
+ mockCurrentUser(OTHER_USER_ID);
+ assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+
+ assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
+ .that(mMediator.getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID);
+ }
+
+ @Test
+ public void testGetUserAssignedToDisplay_noUserOnSecondaryDisplay() {
+ mockCurrentUser(USER_ID);
+
+ assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
+ .that(mMediator.getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID);
+ }
+
+ // TODO(b/244644281): scenario below shouldn't happen on "real life", as the profile cannot be
+ // started on secondary display if its parent isn't, so we might need to remove (or refactor
+ // this test) if/when the underlying logic changes
+ @Test
+ public void testGetUserAssignedToDisplay_profileOnSecondaryDisplay() {
+ addDefaultProfileAndParent();
+ mockCurrentUser(USER_ID);
+ assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+
+ assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
+ .that(mMediator.getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID);
+ }
+
+ // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
+ // getUserAssignedToDisplay() for bg users relies only on the user / display assignments
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
new file mode 100644
index 0000000..7ae8117
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 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.pm;
+
+import static android.os.UserHandle.USER_SYSTEM;
+
+import static org.junit.Assert.assertThrows;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@link UserVisibilityMediator} tests for devices that DO NOT support concurrent
+ * multiple users on multiple displays (A.K.A {@code SUSD} - Single User on Single Device).
+ *
+ * <p>Run as
+ * {@code atest FrameworksMockingServicesTests:com.android.server.pm.UserVisibilityMediatorSUSDTest}
+ */
+public final class UserVisibilityMediatorSUSDTest extends UserVisibilityMediatorTestCase {
+
+ public UserVisibilityMediatorSUSDTest() {
+ super(/* usersOnSecondaryDisplaysEnabled= */ false);
+ }
+
+ @Test
+ public void testAssignUserToDisplay_otherDisplay_currentUser() {
+ mockCurrentUser(USER_ID);
+
+ assertThrows(UnsupportedOperationException.class,
+ () -> mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID));
+ }
+
+ @Test
+ public void testAssignUserToDisplay_otherDisplay_startProfileOfcurrentUser() {
+ mockCurrentUser(PARENT_USER_ID);
+ addDefaultProfileAndParent();
+ startDefaultProfile();
+
+ assertThrows(UnsupportedOperationException.class,
+ () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+ }
+
+ @Test
+ public void testAssignUserToDisplay_otherDisplay_stoppedProfileOfcurrentUser() {
+ mockCurrentUser(PARENT_USER_ID);
+ addDefaultProfileAndParent();
+ stopDefaultProfile();
+
+ assertThrows(UnsupportedOperationException.class,
+ () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+ }
+
+ @Test
+ public void testUnassignUserFromDisplay_ignored() {
+ mockCurrentUser(USER_ID);
+
+ mMediator.unassignUserFromDisplay(USER_SYSTEM);
+ mMediator.unassignUserFromDisplay(USER_ID);
+ mMediator.unassignUserFromDisplay(OTHER_USER_ID);
+
+ assertNoUserAssignedToDisplay();
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
new file mode 100644
index 0000000..22e6e0d
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2022 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.pm;
+
+import static android.os.UserHandle.USER_NULL;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+
+import static com.android.server.am.UserState.STATE_RUNNING_UNLOCKED;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.annotation.UserIdInt;
+import android.util.SparseIntArray;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Base class for {@link UserVisibilityMediator} tests.
+ *
+ * <p>It contains common logics and tests for behaviors that should be invariant regardless of the
+ * device mode (for example, whether the device supports concurrent multiple users on multiple
+ * displays or not).
+ */
+abstract class UserVisibilityMediatorTestCase extends UserManagerServiceOrInternalTestCase {
+
+ /**
+ * Id of a secondary display (i.e, not {@link android.view.Display.DEFAULT_DISPLAY}).
+ */
+ protected static final int SECONDARY_DISPLAY_ID = 42;
+
+ /**
+ * Id of another secondary display (i.e, not {@link android.view.Display.DEFAULT_DISPLAY}).
+ */
+ protected static final int OTHER_SECONDARY_DISPLAY_ID = 108;
+
+ private final boolean mUsersOnSecondaryDisplaysEnabled;
+
+ // TODO(b/244644281): manipulating mUsersOnSecondaryDisplays directly leaks implementation
+ // details into the unit test, but it's fine for now as the tests were copied "as is" - it
+ // would be better to use a geter() instead
+ protected final SparseIntArray mUsersOnSecondaryDisplays = new SparseIntArray();
+
+ protected UserVisibilityMediator mMediator;
+
+ protected UserVisibilityMediatorTestCase(boolean usersOnSecondaryDisplaysEnabled) {
+ mUsersOnSecondaryDisplaysEnabled = usersOnSecondaryDisplaysEnabled;
+ }
+
+ @Before
+ public final void setMediator() {
+ mMediator = new UserVisibilityMediator(mUms, mUsersOnSecondaryDisplaysEnabled,
+ mUsersOnSecondaryDisplays);
+ }
+
+ @Test
+ public final void testAssignUserToDisplay_defaultDisplayIgnored() {
+ mMediator.assignUserToDisplay(USER_ID, DEFAULT_DISPLAY);
+
+ assertNoUserAssignedToDisplay();
+ }
+
+ @Test
+ public final void testIsUserVisible_invalidUser() {
+ mockCurrentUser(USER_ID);
+
+ assertWithMessage("isUserVisible(%s)", USER_NULL)
+ .that(mMediator.isUserVisible(USER_NULL)).isFalse();
+ }
+
+ @Test
+ public final void testIsUserVisible_currentUser() {
+ mockCurrentUser(USER_ID);
+
+ assertWithMessage("isUserVisible(%s)", USER_ID)
+ .that(mMediator.isUserVisible(USER_ID)).isTrue();
+ }
+
+ @Test
+ public final void testIsUserVisible_nonCurrentUser() {
+ mockCurrentUser(OTHER_USER_ID);
+
+ assertWithMessage("isUserVisible(%s)", USER_ID)
+ .that(mMediator.isUserVisible(USER_ID)).isFalse();
+ }
+
+ @Test
+ public final void testIsUserVisible_startedProfileOfcurrentUser() {
+ addDefaultProfileAndParent();
+ mockCurrentUser(PARENT_USER_ID);
+ startDefaultProfile();
+ setUserState(PROFILE_USER_ID, STATE_RUNNING_UNLOCKED);
+
+ assertWithMessage("isUserVisible(%s)", PROFILE_USER_ID)
+ .that(mMediator.isUserVisible(PROFILE_USER_ID)).isTrue();
+ }
+
+ @Test
+ public final void testIsUserVisible_stoppedProfileOfcurrentUser() {
+ addDefaultProfileAndParent();
+ mockCurrentUser(PARENT_USER_ID);
+ stopDefaultProfile();
+
+ assertWithMessage("isUserVisible(%s)", PROFILE_USER_ID)
+ .that(mMediator.isUserVisible(PROFILE_USER_ID)).isFalse();
+ }
+
+ @Test
+ public final void testIsUserVisibleOnDisplay_invalidUser() {
+ mockCurrentUser(USER_ID);
+
+ assertWithMessage("isUserVisible(%s, %s)", USER_NULL, DEFAULT_DISPLAY)
+ .that(mMediator.isUserVisible(USER_NULL, DEFAULT_DISPLAY)).isFalse();
+ }
+
+ @Test
+ public final void testIsUserVisibleOnDisplay_currentUserInvalidDisplay() {
+ mockCurrentUser(USER_ID);
+
+ assertWithMessage("isUserVisible(%s, %s)", USER_ID, INVALID_DISPLAY)
+ .that(mMediator.isUserVisible(USER_ID, INVALID_DISPLAY)).isFalse();
+ }
+
+ @Test
+ public final void testIsUserVisibleOnDisplay_currentUserDefaultDisplay() {
+ mockCurrentUser(USER_ID);
+
+ assertWithMessage("isUserVisible(%s, %s)", USER_ID, DEFAULT_DISPLAY)
+ .that(mMediator.isUserVisible(USER_ID, DEFAULT_DISPLAY)).isTrue();
+ }
+
+ @Test
+ public final void testIsUserVisibleOnDisplay_currentUserSecondaryDisplay() {
+ mockCurrentUser(USER_ID);
+
+ assertWithMessage("isUserVisible(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
+ .that(mMediator.isUserVisible(USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
+ }
+
+ @Test
+ public final void testIsUserVisibleOnDisplay_nonCurrentUserDefaultDisplay() {
+ mockCurrentUser(OTHER_USER_ID);
+
+ assertWithMessage("isUserVisible(%s, %s)", USER_ID, DEFAULT_DISPLAY)
+ .that(mMediator.isUserVisible(USER_ID, DEFAULT_DISPLAY)).isFalse();
+ }
+
+ @Test
+ public final void testIsUserVisibleOnDisplay_startedProfileOfcurrentUserInvalidDisplay() {
+ addDefaultProfileAndParent();
+ mockCurrentUser(PARENT_USER_ID);
+ startDefaultProfile();
+
+ assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, INVALID_DISPLAY)
+ .that(mMediator.isUserVisible(PROFILE_USER_ID, DEFAULT_DISPLAY)).isTrue();
+ }
+
+ @Test
+ public final void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserInvalidDisplay() {
+ addDefaultProfileAndParent();
+ mockCurrentUser(PARENT_USER_ID);
+ stopDefaultProfile();
+
+ assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, INVALID_DISPLAY)
+ .that(mMediator.isUserVisible(PROFILE_USER_ID, DEFAULT_DISPLAY)).isFalse();
+ }
+
+ @Test
+ public final void testIsUserVisibleOnDisplay_startedProfileOfcurrentUserDefaultDisplay() {
+ addDefaultProfileAndParent();
+ mockCurrentUser(PARENT_USER_ID);
+ startDefaultProfile();
+ setUserState(PROFILE_USER_ID, STATE_RUNNING_UNLOCKED);
+
+ assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, DEFAULT_DISPLAY)
+ .that(mMediator.isUserVisible(PROFILE_USER_ID, DEFAULT_DISPLAY)).isTrue();
+ }
+
+ @Test
+ public final void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserDefaultDisplay() {
+ addDefaultProfileAndParent();
+ mockCurrentUser(PARENT_USER_ID);
+ stopDefaultProfile();
+
+ assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, DEFAULT_DISPLAY)
+ .that(mMediator.isUserVisible(PROFILE_USER_ID, DEFAULT_DISPLAY)).isFalse();
+ }
+
+ @Test
+ public final void testIsUserVisibleOnDisplay_startedProfileOfCurrentUserSecondaryDisplay() {
+ addDefaultProfileAndParent();
+ mockCurrentUser(PARENT_USER_ID);
+ startDefaultProfile();
+ setUserState(PROFILE_USER_ID, STATE_RUNNING_UNLOCKED);
+
+ assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
+ .that(mMediator.isUserVisible(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
+ }
+
+ @Test
+ public void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserSecondaryDisplay() {
+ addDefaultProfileAndParent();
+ mockCurrentUser(PARENT_USER_ID);
+ stopDefaultProfile();
+
+ assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
+ .that(mMediator.isUserVisible(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
+ }
+
+ @Test
+ public void testGetDisplayAssignedToUser_invalidUser() {
+ mockCurrentUser(USER_ID);
+
+ assertWithMessage("getDisplayAssignedToUser(%s)", USER_NULL)
+ .that(mMediator.getDisplayAssignedToUser(USER_NULL)).isEqualTo(INVALID_DISPLAY);
+ }
+
+ @Test
+ public void testGetDisplayAssignedToUser_currentUser() {
+ mockCurrentUser(USER_ID);
+
+ assertWithMessage("getDisplayAssignedToUser(%s)", USER_ID)
+ .that(mMediator.getDisplayAssignedToUser(USER_ID)).isEqualTo(DEFAULT_DISPLAY);
+ }
+
+ @Test
+ public final void testGetDisplayAssignedToUser_nonCurrentUser() {
+ mockCurrentUser(OTHER_USER_ID);
+
+ assertWithMessage("getDisplayAssignedToUser(%s)", USER_ID)
+ .that(mMediator.getDisplayAssignedToUser(USER_ID)).isEqualTo(INVALID_DISPLAY);
+ }
+
+ @Test
+ public final void testGetDisplayAssignedToUser_startedProfileOfcurrentUser() {
+ addDefaultProfileAndParent();
+ mockCurrentUser(PARENT_USER_ID);
+ startDefaultProfile();
+ setUserState(PROFILE_USER_ID, STATE_RUNNING_UNLOCKED);
+
+ assertWithMessage("getDisplayAssignedToUser(%s)", PROFILE_USER_ID)
+ .that(mMediator.getDisplayAssignedToUser(PROFILE_USER_ID))
+ .isEqualTo(DEFAULT_DISPLAY);
+ }
+
+ @Test
+ public final void testGetDisplayAssignedToUser_stoppedProfileOfcurrentUser() {
+ addDefaultProfileAndParent();
+ mockCurrentUser(PARENT_USER_ID);
+ stopDefaultProfile();
+
+ assertWithMessage("getDisplayAssignedToUser(%s)", PROFILE_USER_ID)
+ .that(mMediator.getDisplayAssignedToUser(PROFILE_USER_ID))
+ .isEqualTo(INVALID_DISPLAY);
+ }
+
+ @Test
+ public void testGetUserAssignedToDisplay_invalidDisplay() {
+ mockCurrentUser(USER_ID);
+
+ assertWithMessage("getUserAssignedToDisplay(%s)", INVALID_DISPLAY)
+ .that(mMediator.getUserAssignedToDisplay(INVALID_DISPLAY)).isEqualTo(USER_ID);
+ }
+
+ @Test
+ public final void testGetUserAssignedToDisplay_defaultDisplay() {
+ mockCurrentUser(USER_ID);
+
+ assertWithMessage("getUserAssignedToDisplay(%s)", DEFAULT_DISPLAY)
+ .that(mMediator.getUserAssignedToDisplay(DEFAULT_DISPLAY)).isEqualTo(USER_ID);
+ }
+
+ @Test
+ public final void testGetUserAssignedToDisplay_secondaryDisplay() {
+ mockCurrentUser(USER_ID);
+
+ assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
+ .that(mMediator.getUserAssignedToDisplay(SECONDARY_DISPLAY_ID))
+ .isEqualTo(USER_ID);
+ }
+
+ // NOTE: should only called by tests that indirectly needs to check user assignments (like
+ // isUserVisible), not by tests for the user assignment methods per se.
+ protected final void assignUserToDisplay(@UserIdInt int userId, int displayId) {
+ mUsersOnSecondaryDisplays.put(userId, displayId);
+ }
+
+ protected final void assertNoUserAssignedToDisplay() {
+ assertWithMessage("mUsersOnSecondaryDisplays()").that(usersOnSecondaryDisplaysAsMap())
+ .isEmpty();
+ }
+
+ protected final void assertUserAssignedToDisplay(@UserIdInt int userId, int displayId) {
+ assertWithMessage("mUsersOnSecondaryDisplays()").that(usersOnSecondaryDisplaysAsMap())
+ .containsExactly(userId, displayId);
+ }
+
+ private Map<Integer, Integer> usersOnSecondaryDisplaysAsMap() {
+ int size = mUsersOnSecondaryDisplays.size();
+ Map<Integer, Integer> map = new LinkedHashMap<>(size);
+ for (int i = 0; i < size; i++) {
+ map.put(mUsersOnSecondaryDisplays.keyAt(i), mUsersOnSecondaryDisplays.valueAt(i));
+ }
+ return map;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
index 5012335..21c9c75 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
@@ -61,7 +61,7 @@
@Test
public void noopWhenBothNull() {
- final SensorOverlays useless = new SensorOverlays(null, null);
+ final SensorOverlays useless = new SensorOverlays(null, null, null);
useless.show(SENSOR_ID, 2, null);
useless.hide(SENSOR_ID);
}
@@ -69,12 +69,12 @@
@Test
public void testProvidesUdfps() {
final List<IUdfpsOverlayController> udfps = new ArrayList<>();
- SensorOverlays sensorOverlays = new SensorOverlays(null, mSidefpsController);
+ SensorOverlays sensorOverlays = new SensorOverlays(null, mSidefpsController, null);
sensorOverlays.ifUdfps(udfps::add);
assertThat(udfps).isEmpty();
- sensorOverlays = new SensorOverlays(mUdfpsOverlayController, mSidefpsController);
+ sensorOverlays = new SensorOverlays(mUdfpsOverlayController, mSidefpsController, null);
sensorOverlays.ifUdfps(udfps::add);
assertThat(udfps).containsExactly(mUdfpsOverlayController);
}
@@ -96,7 +96,7 @@
private void testShow(IUdfpsOverlayController udfps, ISidefpsController sidefps)
throws Exception {
- final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps);
+ final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps, null);
final int reason = BiometricOverlayConstants.REASON_UNKNOWN;
sensorOverlays.show(SENSOR_ID, reason, mAcquisitionClient);
@@ -126,7 +126,7 @@
private void testHide(IUdfpsOverlayController udfps, ISidefpsController sidefps)
throws Exception {
- final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps);
+ final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps, null);
sensorOverlays.hide(SENSOR_ID);
if (udfps != null) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 1b5db0a..675f0e3 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -645,7 +645,7 @@
9 /* sensorId */, mBiometricLogger, mBiometricContext,
true /* isStrongBiometric */,
null /* taskStackListener */, mLockoutCache,
- mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication,
+ mUdfpsOverlayController, mSideFpsController, null, allowBackgroundAuthentication,
mSensorProps,
new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock) {
@Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
index 93cbef1..4579fc1 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
@@ -118,6 +118,6 @@
return new FingerprintDetectClient(mContext, () -> aidl, mToken,
6 /* requestId */, mClientMonitorCallbackConverter, 2 /* userId */,
"a-test", 1 /* sensorId */, mBiometricLogger, mBiometricContext,
- mUdfpsOverlayController, true /* isStrongBiometric */);
+ mUdfpsOverlayController, null, true /* isStrongBiometric */);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
index 837b553..38b06c4 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -28,7 +28,6 @@
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.same;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -66,7 +65,6 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-import java.util.ArrayList;
import java.util.function.Consumer;
@Presubmit
@@ -292,6 +290,6 @@
mClientMonitorCallbackConverter, 0 /* userId */,
HAT, "owner", mBiometricUtils, 8 /* sensorId */,
mBiometricLogger, mBiometricContext, mSensorProps, mUdfpsOverlayController,
- mSideFpsController, 6 /* maxTemplatesPerUser */, FingerprintManager.ENROLL_ENROLL);
+ mSideFpsController, null, 6 /* maxTemplatesPerUser */, FingerprintManager.ENROLL_ENROLL);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 02bbe65..5fda3d6 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -891,4 +891,34 @@
verify(mContext).startActivityAsUser(argThat(intent ->
intent.filterEquals(blockedAppIntent)), any(), any());
}
+
+ @Test
+ public void registerRunningAppsChangedListener_onRunningAppsChanged_listenersNotified() {
+ ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
+ mDeviceImpl.onVirtualDisplayCreatedLocked(
+ mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ DISPLAY_ID);
+
+ gwpc.onRunningAppsChanged(uids);
+ mDeviceImpl.onRunningAppsChanged(uids);
+
+ assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(1);
+ verify(mRunningAppsChangedCallback).accept(new ArraySet<>(Arrays.asList(UID_1, UID_2)));
+ }
+
+ @Test
+ public void noRunningAppsChangedListener_onRunningAppsChanged_doesNotThrowException() {
+ ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
+ mDeviceImpl.onVirtualDisplayCreatedLocked(
+ mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ DISPLAY_ID);
+ mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID);
+
+ // This call should not throw any exceptions.
+ gwpc.onRunningAppsChanged(uids);
+
+ assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(0);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
index c68db34..6590a2b 100644
--- a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
@@ -36,6 +36,7 @@
import com.android.server.input.BatteryController.POLLING_PERIOD_MILLIS
import com.android.server.input.BatteryController.UEventManager
import com.android.server.input.BatteryController.UEventManager.UEventBatteryListener
+import com.android.server.input.BatteryController.USI_BATTERY_VALIDITY_DURATION_MILLIS
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.MatcherAssert.assertThat
@@ -528,10 +529,109 @@
matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.64f))
// The battery is no longer present after the timeout expires.
- testLooper.moveTimeForward(BatteryController.USI_BATTERY_VALIDITY_DURATION_MILLIS - 1)
+ testLooper.moveTimeForward(USI_BATTERY_VALIDITY_DURATION_MILLIS - 1)
testLooper.dispatchNext()
listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID), times(2))
assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
isInvalidBatteryState(USI_DEVICE_ID))
}
+
+ @Test
+ fun testStylusPresenceExtendsValidUsiBatteryState() {
+ `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device")
+ `when`(native.getBatteryStatus(USI_DEVICE_ID)).thenReturn(STATUS_DISCHARGING)
+ `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(78)
+
+ addInputDevice(USI_DEVICE_ID, supportsUsi = true)
+ testLooper.dispatchNext()
+ val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
+ verify(uEventManager)
+ .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device"))
+
+ // There is a UEvent signaling a battery change. The battery state is now valid.
+ uEventListener.value!!.onBatteryUEvent(TIMESTAMP)
+ val listener = createMockListener()
+ batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID)
+ listener.verifyNotified(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f)
+ assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+ matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f))
+
+ // Stylus presence is detected before the validity timeout expires.
+ testLooper.moveTimeForward(100)
+ testLooper.dispatchAll()
+ batteryController.notifyStylusGestureStarted(USI_DEVICE_ID, TIMESTAMP)
+
+ // Ensure that timeout was extended, and the battery state is now valid for longer.
+ testLooper.moveTimeForward(USI_BATTERY_VALIDITY_DURATION_MILLIS - 100)
+ testLooper.dispatchAll()
+ assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+ matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f))
+
+ // Ensure the validity period expires after the expected amount of time.
+ testLooper.moveTimeForward(100)
+ testLooper.dispatchNext()
+ listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID))
+ assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+ isInvalidBatteryState(USI_DEVICE_ID))
+ }
+
+ @Test
+ fun testStylusPresenceMakesUsiBatteryStateValid() {
+ `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device")
+ `when`(native.getBatteryStatus(USI_DEVICE_ID)).thenReturn(STATUS_DISCHARGING)
+ `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(78)
+
+ addInputDevice(USI_DEVICE_ID, supportsUsi = true)
+ testLooper.dispatchNext()
+ val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
+ verify(uEventManager)
+ .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device"))
+
+ // The USI battery state is initially invalid.
+ val listener = createMockListener()
+ batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID)
+ listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID))
+ assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+ isInvalidBatteryState(USI_DEVICE_ID))
+
+ // A stylus presence is detected. This validates the battery state.
+ batteryController.notifyStylusGestureStarted(USI_DEVICE_ID, TIMESTAMP)
+
+ listener.verifyNotified(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f)
+ assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+ matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f))
+ }
+
+ @Test
+ fun testStylusPresenceDoesNotMakeUsiBatteryStateValidAtBoot() {
+ `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device")
+ // At boot, the USI device always reports a capacity value of 0.
+ `when`(native.getBatteryStatus(USI_DEVICE_ID)).thenReturn(STATUS_UNKNOWN)
+ `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(0)
+
+ addInputDevice(USI_DEVICE_ID, supportsUsi = true)
+ testLooper.dispatchNext()
+ val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
+ verify(uEventManager)
+ .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device"))
+
+ // The USI battery state is initially invalid.
+ val listener = createMockListener()
+ batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID)
+ listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID))
+ assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+ isInvalidBatteryState(USI_DEVICE_ID))
+
+ // Since the capacity reported is 0, stylus presence does not validate the battery state.
+ batteryController.notifyStylusGestureStarted(USI_DEVICE_ID, TIMESTAMP)
+
+ assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+ isInvalidBatteryState(USI_DEVICE_ID))
+
+ // However, if a UEvent reports a battery capacity of 0, the battery state is now valid.
+ uEventListener.value!!.onBatteryUEvent(TIMESTAMP)
+ listener.verifyNotified(USI_DEVICE_ID, status = STATUS_UNKNOWN, capacity = 0f)
+ assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+ matchesState(USI_DEVICE_ID, status = STATUS_UNKNOWN, capacity = 0f))
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
index a5fedef..21d2784 100644
--- a/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
@@ -36,7 +36,6 @@
import com.android.server.job.JobConcurrencyManager.WorkTypeConfig;
-import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -59,30 +58,6 @@
private static final String KEY_MIN_BGUSER_IMPORTANT = "concurrency_min_bguser_important_test";
private static final String KEY_MIN_BGUSER = "concurrency_min_bguser_test";
- @After
- public void tearDown() throws Exception {
- resetConfig();
- }
-
- private void resetConfig() {
- // DeviceConfig.resetToDefaults() doesn't work here. Need to reset constants manually.
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOTAL, null, false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOP, null, false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_FGS, null, false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_EJ, null, false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_BG, null, false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
- KEY_MAX_BGUSER_IMPORTANT, null, false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_BGUSER, null, false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_TOP, null, false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_FGS, null, false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_EJ, null, false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BG, null, false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
- KEY_MIN_BGUSER_IMPORTANT, null, false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BGUSER, null, false);
- }
-
private void check(@Nullable DeviceConfig.Properties config,
int defaultTotal,
@NonNull List<Pair<Integer, Integer>> defaultMin,
@@ -90,10 +65,6 @@
boolean expectedValid, int expectedTotal,
@NonNull List<Pair<Integer, Integer>> expectedMinLimits,
@NonNull List<Pair<Integer, Integer>> expectedMaxLimits) throws Exception {
- resetConfig();
- if (config != null) {
- DeviceConfig.setProperties(config);
- }
final WorkTypeConfig counts;
try {
@@ -112,7 +83,9 @@
}
}
- counts.update(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_JOB_SCHEDULER));
+ if (config != null) {
+ counts.update(config);
+ }
assertEquals(expectedTotal, counts.getMaxTotal());
for (Pair<Integer, Integer> min : expectedMinLimits) {
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
index dbcd38c..dc9f907 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
@@ -229,6 +229,29 @@
}
}
+ @Test(expected = SecurityException.class)
+ public void testGetApplicationLocales_currentImeQueryNonForegroundAppLocales_fails()
+ throws Exception {
+ doReturn(DEFAULT_UID).when(mMockPackageManager)
+ .getPackageUidAsUser(anyString(), any(), anyInt());
+ doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
+ .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
+ String imPkgName = getCurrentInputMethodPackageName();
+ doReturn(Binder.getCallingUid()).when(mMockPackageManager)
+ .getPackageUidAsUser(eq(imPkgName), any(), anyInt());
+ doReturn(false).when(mMockActivityManager).isAppForeground(anyInt());
+ setUpFailingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
+
+ try {
+ mLocaleManagerService.getApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ fail("Expected SecurityException");
+ } finally {
+ verify(mMockContext).enforceCallingOrSelfPermission(
+ eq(android.Manifest.permission.READ_APP_SPECIFIC_LOCALES),
+ anyString());
+ }
+ }
+
@Test
public void testGetApplicationLocales_appSpecificConfigAbsent_returnsEmptyList()
throws Exception {
@@ -307,7 +330,7 @@
}
@Test
- public void testGetApplicationLocales_callerIsCurrentInputMethod_returnsLocales()
+ public void testGetApplicationLocales_currentImeQueryForegroundAppLocales_returnsLocales()
throws Exception {
doReturn(DEFAULT_UID).when(mMockPackageManager)
.getPackageUidAsUser(anyString(), any(), anyInt());
@@ -316,6 +339,7 @@
String imPkgName = getCurrentInputMethodPackageName();
doReturn(Binder.getCallingUid()).when(mMockPackageManager)
.getPackageUidAsUser(eq(imPkgName), any(), anyInt());
+ doReturn(true).when(mMockActivityManager).isAppForeground(anyInt());
LocaleList locales =
mLocaleManagerService.getApplicationLocales(
diff --git a/services/tests/servicestests/src/com/android/server/utils/EventLoggerTest.java b/services/tests/servicestests/src/com/android/server/utils/EventLoggerTest.java
index 0b27f87..4762696 100644
--- a/services/tests/servicestests/src/com/android/server/utils/EventLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/utils/EventLoggerTest.java
@@ -125,7 +125,7 @@
@Test
public void testThatLoggingWorksAsExpected() {
for (EventLogger.Event event: mEventsToInsert) {
- mEventLogger.log(event);
+ mEventLogger.enqueue(event);
}
mEventLogger.dump(mTestPrintWriter);
diff --git a/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java
index a917c57..6ef5b0e 100644
--- a/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java
@@ -51,6 +51,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -87,6 +88,7 @@
LocalServices.removeServiceForTest(UsageStatsManagerInternal.class);
}
+ @Ignore("b/253871109")
@Test
public void testAddPinCreatesPinned() throws RemoteException {
grantSlicePermission();
@@ -97,6 +99,7 @@
verify(mService, times(1)).createPinnedSlice(eq(maybeAddUserId(TEST_URI, 0)), anyString());
}
+ @Ignore("b/253871109")
@Test
public void testRemovePinDestroysPinned() throws RemoteException {
grantSlicePermission();
@@ -109,6 +112,7 @@
verify(mCreatedSliceState, never()).destroy();
}
+ @Ignore("b/253871109")
@Test
public void testCheckAutoGrantPermissions() throws RemoteException {
String[] testPerms = new String[] {
@@ -128,12 +132,14 @@
verify(mContextSpy).checkPermission(eq("perm2"), eq(Process.myPid()), eq(Process.myUid()));
}
+ @Ignore("b/253871109")
@Test(expected = IllegalStateException.class)
public void testNoPinThrow() throws Exception {
grantSlicePermission();
mService.getPinnedSpecs(TEST_URI, "pkg");
}
+ @Ignore("b/253871109")
@Test
public void testGetPinnedSpecs() throws Exception {
grantSlicePermission();
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 439eaa6..ef693b5 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -3942,6 +3942,10 @@
* may provide one. Or, a carrier may decide to provide the phone number via source
* {@link #PHONE_NUMBER_SOURCE_CARRIER carrier} if neither source UICC nor IMS is available.
*
+ * <p>The availability and correctness of the phone number depends on the underlying source
+ * and the network etc. Additional verification is needed to use this number for
+ * security-related or other sensitive scenarios.
+ *
* @param subscriptionId the subscription ID, or {@link #DEFAULT_SUBSCRIPTION_ID}
* for the default one.
* @param source the source of the phone number, one of the PHONE_NUMBER_SOURCE_* constants.
@@ -4175,18 +4179,18 @@
*/
@SystemApi
@RequiresPermission(Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION)
- public void setUserHandle(int subscriptionId, @Nullable UserHandle userHandle) {
+ public void setSubscriptionUserHandle(int subscriptionId, @Nullable UserHandle userHandle) {
if (!isValidSubscriptionId(subscriptionId)) {
- throw new IllegalArgumentException("[setUserHandle]: Invalid subscriptionId: "
- + subscriptionId);
+ throw new IllegalArgumentException("[setSubscriptionUserHandle]: "
+ + "Invalid subscriptionId: " + subscriptionId);
}
try {
ISub iSub = TelephonyManager.getSubscriptionService();
if (iSub != null) {
- iSub.setUserHandle(userHandle, subscriptionId, mContext.getOpPackageName());
+ iSub.setSubscriptionUserHandle(userHandle, subscriptionId);
} else {
- throw new IllegalStateException("[setUserHandle]: "
+ throw new IllegalStateException("[setSubscriptionUserHandle]: "
+ "subscription service unavailable");
}
} catch (RemoteException ex) {
@@ -4211,18 +4215,18 @@
*/
@SystemApi
@RequiresPermission(Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION)
- public @Nullable UserHandle getUserHandle(int subscriptionId) {
+ public @Nullable UserHandle getSubscriptionUserHandle(int subscriptionId) {
if (!isValidSubscriptionId(subscriptionId)) {
- throw new IllegalArgumentException("[getUserHandle]: Invalid subscriptionId: "
- + subscriptionId);
+ throw new IllegalArgumentException("[getSubscriptionUserHandle]: "
+ + "Invalid subscriptionId: " + subscriptionId);
}
try {
ISub iSub = TelephonyManager.getSubscriptionService();
if (iSub != null) {
- return iSub.getUserHandle(subscriptionId, mContext.getOpPackageName());
+ return iSub.getSubscriptionUserHandle(subscriptionId);
} else {
- throw new IllegalStateException("[getUserHandle]: "
+ throw new IllegalStateException("[getSubscriptionUserHandle]: "
+ "subscription service unavailable");
}
} catch (RemoteException ex) {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 9ecebf1..ff1b1c0 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3323,15 +3323,14 @@
case NETWORK_TYPE_TD_SCDMA:
return NETWORK_TYPE_BITMASK_TD_SCDMA;
case NETWORK_TYPE_LTE:
- return NETWORK_TYPE_BITMASK_LTE;
case NETWORK_TYPE_LTE_CA:
- return NETWORK_TYPE_BITMASK_LTE_CA;
+ return NETWORK_TYPE_BITMASK_LTE;
case NETWORK_TYPE_NR:
return NETWORK_TYPE_BITMASK_NR;
case NETWORK_TYPE_IWLAN:
return NETWORK_TYPE_BITMASK_IWLAN;
case NETWORK_TYPE_IDEN:
- return (1 << (NETWORK_TYPE_IDEN - 1));
+ return NETWORK_TYPE_BITMASK_IDEN;
default:
return NETWORK_TYPE_BITMASK_UNKNOWN;
}
@@ -13911,7 +13910,8 @@
NETWORK_TYPE_BITMASK_LTE,
NETWORK_TYPE_BITMASK_LTE_CA,
NETWORK_TYPE_BITMASK_NR,
- NETWORK_TYPE_BITMASK_IWLAN
+ NETWORK_TYPE_BITMASK_IWLAN,
+ NETWORK_TYPE_BITMASK_IDEN
})
public @interface NetworkTypeBitMask {}
@@ -13971,6 +13971,10 @@
*/
public static final long NETWORK_TYPE_BITMASK_HSPA = (1 << (NETWORK_TYPE_HSPA -1));
/**
+ * network type bitmask indicating the support of radio tech iDen.
+ */
+ public static final long NETWORK_TYPE_BITMASK_IDEN = (1 << (NETWORK_TYPE_IDEN - 1));
+ /**
* network type bitmask indicating the support of radio tech HSPAP.
*/
public static final long NETWORK_TYPE_BITMASK_HSPAP = (1 << (NETWORK_TYPE_HSPAP -1));
@@ -13988,12 +13992,13 @@
*/
public static final long NETWORK_TYPE_BITMASK_LTE = (1 << (NETWORK_TYPE_LTE -1));
/**
- * NOT USED; this bitmask is exposed accidentally, will be deprecated in U.
+ * NOT USED; this bitmask is exposed accidentally.
* If used, will be converted to {@link #NETWORK_TYPE_BITMASK_LTE}.
* network type bitmask indicating the support of radio tech LTE CA (carrier aggregation).
*
- * @see #NETWORK_TYPE_BITMASK_LTE
+ * @deprecated Please use {@link #NETWORK_TYPE_BITMASK_LTE} instead.
*/
+ @Deprecated
public static final long NETWORK_TYPE_BITMASK_LTE_CA = (1 << (NETWORK_TYPE_LTE_CA -1));
/**
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index f0401534..e8642fe 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -204,6 +204,7 @@
private IImsServiceControllerListener mListener;
private final Object mListenerLock = new Object();
+ private final Object mExecutorLock = new Object();
private Executor mExecutor;
/**
@@ -214,10 +215,6 @@
* vendor use Runnable::run.
*/
public ImsService() {
- mExecutor = ImsService.this.getExecutor();
- if (mExecutor == null) {
- mExecutor = Runnable::run;
- }
}
/**
@@ -356,7 +353,7 @@
ImsConfigImplBase c =
ImsService.this.getConfigForSubscription(slotId, subId);
if (c != null) {
- c.setDefaultExecutor(mExecutor);
+ c.setDefaultExecutor(getCachedExecutor());
return c.getIImsConfig();
} else {
return null;
@@ -370,7 +367,7 @@
ImsRegistrationImplBase r =
ImsService.this.getRegistrationForSubscription(slotId, subId);
if (r != null) {
- r.setDefaultExecutor(mExecutor);
+ r.setDefaultExecutor(getCachedExecutor());
return r.getBinder();
} else {
return null;
@@ -383,7 +380,7 @@
return executeMethodAsyncForResult(() -> {
SipTransportImplBase s = ImsService.this.getSipTransport(slotId);
if (s != null) {
- s.setDefaultExecutor(mExecutor);
+ s.setDefaultExecutor(getCachedExecutor());
return s.getBinder();
} else {
return null;
@@ -427,11 +424,21 @@
return null;
}
+ private Executor getCachedExecutor() {
+ synchronized (mExecutorLock) {
+ if (mExecutor == null) {
+ Executor e = ImsService.this.getExecutor();
+ mExecutor = (e != null) ? e : Runnable::run;
+ }
+ return mExecutor;
+ }
+ }
+
private IImsMmTelFeature createMmTelFeatureInternal(int slotId, int subscriptionId) {
MmTelFeature f = createMmTelFeatureForSubscription(slotId, subscriptionId);
if (f != null) {
setupFeature(f, slotId, ImsFeature.FEATURE_MMTEL);
- f.setDefaultExecutor(mExecutor);
+ f.setDefaultExecutor(getCachedExecutor());
return f.getBinder();
} else {
Log.e(LOG_TAG, "createMmTelFeatureInternal: null feature returned.");
@@ -443,7 +450,7 @@
MmTelFeature f = createEmergencyOnlyMmTelFeature(slotId);
if (f != null) {
setupFeature(f, slotId, ImsFeature.FEATURE_MMTEL);
- f.setDefaultExecutor(mExecutor);
+ f.setDefaultExecutor(getCachedExecutor());
return f.getBinder();
} else {
Log.e(LOG_TAG, "createEmergencyOnlyMmTelFeatureInternal: null feature returned.");
@@ -454,7 +461,7 @@
private IImsRcsFeature createRcsFeatureInternal(int slotId, int subI) {
RcsFeature f = createRcsFeatureForSubscription(slotId, subI);
if (f != null) {
- f.setDefaultExecutor(mExecutor);
+ f.setDefaultExecutor(getCachedExecutor());
setupFeature(f, slotId, ImsFeature.FEATURE_RCS);
return f.getBinder();
} else {
@@ -609,7 +616,8 @@
private void executeMethodAsync(Runnable r, String errorLogName) {
try {
CompletableFuture.runAsync(
- () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor).join();
+ () -> TelephonyUtils.runWithCleanCallingIdentity(r),
+ getCachedExecutor()).join();
} catch (CancellationException | CompletionException e) {
Log.w(LOG_TAG, "ImsService Binder - " + errorLogName + " exception: "
+ e.getMessage());
@@ -618,7 +626,7 @@
private <T> T executeMethodAsyncForResult(Supplier<T> r, String errorLogName) {
CompletableFuture<T> future = CompletableFuture.supplyAsync(
- () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor);
+ () -> TelephonyUtils.runWithCleanCallingIdentity(r), getCachedExecutor());
try {
return future.get();
} catch (ExecutionException | InterruptedException e) {
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 0211a7f..4752cca 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -323,22 +323,20 @@
*
* @param userHandle the user handle for this subscription
* @param subId the unique SubscriptionInfo index in database
- * @param callingPackage The package making the IPC.
*
* @throws SecurityException if doesn't have MANAGE_SUBSCRIPTION_USER_ASSOCIATION
* @throws IllegalArgumentException if subId is invalid.
*/
- int setUserHandle(in UserHandle userHandle, int subId, String callingPackage);
+ int setSubscriptionUserHandle(in UserHandle userHandle, int subId);
/**
* Get UserHandle for this subscription
*
* @param subId the unique SubscriptionInfo index in database
- * @param callingPackage the package making the IPC
* @return userHandle associated with this subscription.
*
- * @throws SecurityException if doesn't have SMANAGE_SUBSCRIPTION_USER_ASSOCIATION
+ * @throws SecurityException if doesn't have MANAGE_SUBSCRIPTION_USER_ASSOCIATION
* @throws IllegalArgumentException if subId is invalid.
*/
- UserHandle getUserHandle(int subId, String callingPackage);
+ UserHandle getSubscriptionUserHandle(int subId);
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
index 16753e6..ca5b2af 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
@@ -24,6 +24,8 @@
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.Condition
+import com.android.server.wm.traces.common.DeviceStateDump
import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import java.util.regex.Pattern
@@ -47,9 +49,10 @@
wmHelper: WindowManagerStateHelper,
expectedWindowName: String,
action: String?,
- stringExtras: Map<String, String>
+ stringExtras: Map<String, String>,
+ waitConditions: Array<Condition<DeviceStateDump>>
) {
- super.launchViaIntent(wmHelper, expectedWindowName, action, stringExtras)
+ super.launchViaIntent(wmHelper, expectedWindowName, action, stringExtras, waitConditions)
waitIMEShown(wmHelper)
}