Merge "Remove InputMethodManagerDelegate#startInput()"
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 761d9fa..bf02ff0 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -9276,13 +9276,17 @@
}
public final class BugreportManager {
+ method @RequiresPermission(android.Manifest.permission.DUMP) @WorkerThread public void preDumpUiData();
method @RequiresPermission(android.Manifest.permission.DUMP) public void requestBugreport(@NonNull android.os.BugreportParams, @Nullable CharSequence, @Nullable CharSequence);
method @RequiresPermission(android.Manifest.permission.DUMP) @WorkerThread public void startBugreport(@NonNull android.os.ParcelFileDescriptor, @Nullable android.os.ParcelFileDescriptor, @NonNull android.os.BugreportParams, @NonNull java.util.concurrent.Executor, @NonNull android.os.BugreportManager.BugreportCallback);
}
public final class BugreportParams {
ctor public BugreportParams(int);
+ ctor public BugreportParams(int, int);
+ method public int getFlags();
method public int getMode();
+ field public static final int BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA = 1; // 0x1
field public static final int BUGREPORT_MODE_FULL = 0; // 0x0
field public static final int BUGREPORT_MODE_INTERACTIVE = 1; // 0x1
field public static final int BUGREPORT_MODE_REMOTE = 2; // 0x2
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index 73bb8d5..222e88f 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -148,6 +148,29 @@
}
/**
+ * Speculatively pre-dumps UI data for a bugreport request that might come later.
+ *
+ * <p>Triggers the dump of certain critical UI data, e.g. traces stored in short
+ * ring buffers that might get lost by the time the actual bugreport is requested.
+ *
+ * <p>{@link #startBugreport} will then pick the pre-dumped data if both of the following
+ * conditions are met:
+ * - {@link android.os.BugreportParams#BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA} is specified.
+ * - {@link #preDumpUiData} and {@link #startBugreport} were called by the same UID.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.DUMP)
+ @WorkerThread
+ public void preDumpUiData() {
+ try {
+ mBinder.preDumpUiData(mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Starts a bugreport.
*
* <p>This starts a bugreport in the background. However the call itself can take several
@@ -198,6 +221,7 @@
bugreportFd.getFileDescriptor(),
screenshotFd.getFileDescriptor(),
params.getMode(),
+ params.getFlags(),
dsListener,
isScreenshotRequested);
} catch (RemoteException e) {
diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java
index 279ccae..990883f 100644
--- a/core/java/android/os/BugreportParams.java
+++ b/core/java/android/os/BugreportParams.java
@@ -30,16 +30,46 @@
@SystemApi
public final class BugreportParams {
private final int mMode;
+ private final int mFlags;
+ /**
+ * Constructs a BugreportParams object to specify what kind of bugreport should be taken.
+ *
+ * @param mode of the bugreport to request
+ */
public BugreportParams(@BugreportMode int mode) {
mMode = mode;
+ mFlags = 0;
}
+ /**
+ * Constructs a BugreportParams object to specify what kind of bugreport should be taken.
+ *
+ * @param mode of the bugreport to request
+ * @param flags to customize the bugreport request
+ */
+ public BugreportParams(@BugreportMode int mode, @BugreportFlag int flags) {
+ mMode = mode;
+ mFlags = flags;
+ }
+
+ /**
+ * Returns the mode of the bugreport to request.
+ */
+ @BugreportMode
public int getMode() {
return mMode;
}
/**
+ * Returns the flags to customize the bugreport request.
+ */
+ @BugreportFlag
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /**
* Defines acceptable types of bugreports.
* @hide
*/
@@ -88,4 +118,21 @@
* Wifi.
*/
public static final int BUGREPORT_MODE_WIFI = IDumpstate.BUGREPORT_MODE_WIFI;
+
+ /**
+ * Defines acceptable flags for customizing bugreport requests.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "BUGREPORT_FLAG_" }, value = {
+ BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA
+ })
+ public @interface BugreportFlag {}
+
+ /**
+ * Flag for reusing pre-dumped UI data. The pre-dump and bugreport request calls must be
+ * performed by the same UID, otherwise the flag is ignored.
+ */
+ public static final int BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA =
+ IDumpstate.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA;
}
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java
new file mode 100644
index 0000000..57b9cb1
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java
@@ -0,0 +1,407 @@
+/*
+ * 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.junit.Assert.assertThrows;
+
+import android.annotation.Nullable;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+
+import org.junit.Test;
+
+public final class ProgramSelectorTest {
+
+ private static final int FM_PROGRAM_TYPE = ProgramSelector.PROGRAM_TYPE_FM;
+ private static final int DAB_PROGRAM_TYPE = ProgramSelector.PROGRAM_TYPE_DAB;
+ private static final long FM_FREQUENCY = 88500;
+ private static final long AM_FREQUENCY = 700;
+ private static final ProgramSelector.Identifier FM_IDENTIFIER = new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, FM_FREQUENCY);
+ private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER_1 =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
+ /* value= */ 0x1000011);
+ private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER_2 =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
+ /* value= */ 0x10000112);
+ private static final ProgramSelector.Identifier DAB_ENSEMBLE_IDENTIFIER =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
+ /* value= */ 0x1001);
+ private static final ProgramSelector.Identifier DAB_FREQUENCY_IDENTIFIER =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY,
+ /* value= */ 94500);
+
+ @Test
+ public void getType_forIdentifier() {
+ assertWithMessage("Identifier type").that(FM_IDENTIFIER.getType())
+ .isEqualTo(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY);
+ }
+
+ @Test
+ public void isCategoryType_withCategoryTypeForIdentifier() {
+ int typeChecked = ProgramSelector.IDENTIFIER_TYPE_VENDOR_START + 1;
+ ProgramSelector.Identifier fmIdentifier = new ProgramSelector.Identifier(
+ typeChecked, /* value= */ 99901);
+
+ assertWithMessage("Whether %s is a category identifier type", typeChecked)
+ .that(fmIdentifier.isCategoryType()).isTrue();
+ }
+
+ @Test
+ public void isCategoryType_withNonCategoryTypeForIdentifier() {
+ assertWithMessage("Is AMFM_FREQUENCY category identifier type")
+ .that(FM_IDENTIFIER.isCategoryType()).isFalse();
+ }
+
+ @Test
+ public void getValue_forIdentifier() {
+ assertWithMessage("Identifier value")
+ .that(FM_IDENTIFIER.getValue()).isEqualTo(FM_FREQUENCY);
+ }
+
+ @Test
+ public void equals_withDifferentTypesForIdentifiers_returnsFalse() {
+ assertWithMessage("Identifier with different identifier type")
+ .that(FM_IDENTIFIER).isNotEqualTo(DAB_SID_EXT_IDENTIFIER_1);
+ }
+
+ @Test
+ public void equals_withDifferentValuesForIdentifiers_returnsFalse() {
+ assertWithMessage("Identifier with different identifier value")
+ .that(DAB_SID_EXT_IDENTIFIER_2).isNotEqualTo(DAB_SID_EXT_IDENTIFIER_1);
+ }
+
+ @Test
+ public void equals_withSameKeyAndValueForIdentifiers_returnsTrue() {
+ ProgramSelector.Identifier fmIdentifierSame = new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, FM_FREQUENCY);
+
+ assertWithMessage("Identifier of the same identifier")
+ .that(FM_IDENTIFIER).isEqualTo(fmIdentifierSame);
+ }
+
+ @Test
+ public void getProgramType() {
+ ProgramSelector selector = getFmSelector(/* secondaryIds= */ null, /* vendorIds= */ null);
+
+ int programType = selector.getProgramType();
+
+ assertWithMessage("Program type").that(programType).isEqualTo(FM_PROGRAM_TYPE);
+ }
+
+ @Test
+ public void getPrimaryId() {
+ ProgramSelector selector = getFmSelector(/* secondaryIds= */ null, /* vendorIds= */ null);
+
+ ProgramSelector.Identifier programId = selector.getPrimaryId();
+
+ assertWithMessage("Program Id").that(programId).isEqualTo(FM_IDENTIFIER);
+ }
+
+ @Test
+ public void getSecondaryIds_withEmptySecondaryIds() {
+ ProgramSelector selector = getFmSelector(/* secondaryIds= */ null, /* vendorIds= */ null);
+
+ ProgramSelector.Identifier[] secondaryIds = selector.getSecondaryIds();
+
+ assertWithMessage("Secondary ids of selector initialized with empty secondary ids")
+ .that(secondaryIds).isEmpty();
+ }
+
+ @Test
+ public void getSecondaryIds_withNonEmptySecondaryIds() {
+ ProgramSelector.Identifier[] secondaryIdsExpected = new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER};
+ ProgramSelector selector = getDabSelector(secondaryIdsExpected, /* vendorIds= */ null);
+
+ ProgramSelector.Identifier[] secondaryIds = selector.getSecondaryIds();
+
+ assertWithMessage("Secondary identifier got")
+ .that(secondaryIds).isEqualTo(secondaryIdsExpected);
+ }
+
+ @Test
+ public void getFirstId_withIdInSelector() {
+ ProgramSelector.Identifier[] secondaryIds = new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_SID_EXT_IDENTIFIER_2, DAB_FREQUENCY_IDENTIFIER};
+ ProgramSelector selector = getDabSelector(secondaryIds, /* vendorIds= */ null);
+
+ long firstIdValue = selector.getFirstId(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT);
+
+ assertWithMessage("Value of the first DAB_SID_EXT identifier")
+ .that(firstIdValue).isEqualTo(DAB_SID_EXT_IDENTIFIER_1.getValue());
+ }
+
+ @Test
+ public void getFirstId_withIdNotInSelector() {
+ ProgramSelector.Identifier[] secondaryIds = new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_SID_EXT_IDENTIFIER_2};
+ ProgramSelector selector = getDabSelector(secondaryIds, /* vendorIds= */ null);
+
+ int idType = ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY;
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+ selector.getFirstId(idType);
+ });
+
+ assertWithMessage("Exception for getting first identifier %s", idType)
+ .that(thrown).hasMessageThat().contains("Identifier " + idType + " not found");
+ }
+
+ @Test
+ public void getAllIds_withIdInSelector() {
+ ProgramSelector.Identifier[] secondaryIds = new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_SID_EXT_IDENTIFIER_2, DAB_FREQUENCY_IDENTIFIER};
+ ProgramSelector.Identifier[] allIdsExpected =
+ {DAB_SID_EXT_IDENTIFIER_1, DAB_SID_EXT_IDENTIFIER_2};
+ ProgramSelector selector = getDabSelector(secondaryIds, /* vendorIds= */ null);
+
+ ProgramSelector.Identifier[] allIds =
+ selector.getAllIds(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT);
+
+ assertWithMessage("All DAB_SID_EXT identifiers in selector")
+ .that(allIds).isEqualTo(allIdsExpected);
+ }
+
+ @Test
+ public void getAllIds_withIdNotInSelector() {
+ ProgramSelector.Identifier[] secondaryIds = new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER};
+ ProgramSelector selector = getDabSelector(secondaryIds, /* vendorIds= */ null);
+
+ ProgramSelector.Identifier[] allIds =
+ selector.getAllIds(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY);
+
+ assertWithMessage("AMFM frequency identifiers found in selector")
+ .that(allIds).isEmpty();
+ }
+
+ @Test
+ public void getVendorIds_withEmptyVendorIds() {
+ ProgramSelector selector = getFmSelector(/* secondaryIds= */ null, /* vendorIds= */ null);
+
+ long[] vendorIds = selector.getVendorIds();
+
+ assertWithMessage("Vendor Ids of selector initialized with empty vendor ids")
+ .that(vendorIds).isEmpty();
+ }
+
+ @Test
+ public void getVendorIds_withNonEmptyVendorIds() {
+ long[] vendorIdsExpected = {12345, 678};
+ ProgramSelector selector = getFmSelector(/* secondaryIds= */ null, vendorIdsExpected);
+
+ long[] vendorIds = selector.getVendorIds();
+
+ assertWithMessage("Vendor Ids of selector initialized with non-empty vendor ids")
+ .that(vendorIds).isEqualTo(vendorIdsExpected);
+ }
+
+ @Test
+ public void withSecondaryPreferred() {
+ ProgramSelector.Identifier[] secondaryIds = new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_SID_EXT_IDENTIFIER_2, DAB_FREQUENCY_IDENTIFIER};
+ long[] vendorIdsExpected = {12345, 678};
+ ProgramSelector selector = getDabSelector(secondaryIds, vendorIdsExpected);
+ ProgramSelector.Identifier[] secondaryIdsExpected = new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER, DAB_SID_EXT_IDENTIFIER_1};
+
+ ProgramSelector selectorPreferred =
+ selector.withSecondaryPreferred(DAB_SID_EXT_IDENTIFIER_1);
+
+ assertWithMessage("Program type")
+ .that(selectorPreferred.getProgramType()).isEqualTo(selector.getProgramType());
+ assertWithMessage("Primary identifiers")
+ .that(selectorPreferred.getPrimaryId()).isEqualTo(selector.getPrimaryId());
+ assertWithMessage("Secondary identifiers")
+ .that(selectorPreferred.getSecondaryIds()).isEqualTo(secondaryIdsExpected);
+ assertWithMessage("Vendor Ids")
+ .that(selectorPreferred.getVendorIds()).isEqualTo(vendorIdsExpected);
+ }
+
+ @Test
+ public void createAmFmSelector_withValidFrequencyWithoutSubChannel() {
+ int band = RadioManager.BAND_AM;
+ ProgramSelector.Identifier primaryIdExpected = new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, AM_FREQUENCY);
+
+ ProgramSelector selector = ProgramSelector.createAmFmSelector(band, (int) AM_FREQUENCY);
+
+ assertWithMessage("Program type")
+ .that(selector.getProgramType()).isEqualTo(ProgramSelector.PROGRAM_TYPE_AM);
+ assertWithMessage("Primary identifiers")
+ .that(selector.getPrimaryId()).isEqualTo(primaryIdExpected);
+ assertWithMessage("Secondary identifiers")
+ .that(selector.getSecondaryIds()).isEmpty();
+ }
+
+ @Test
+ public void createAmFmSelector_withoutBandAndSubChannel() {
+ ProgramSelector.Identifier primaryIdExpected = new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, FM_FREQUENCY);
+
+ ProgramSelector selector = ProgramSelector.createAmFmSelector(
+ RadioManager.BAND_INVALID, (int) FM_FREQUENCY);
+
+ assertWithMessage("Program type")
+ .that(selector.getProgramType()).isEqualTo(ProgramSelector.PROGRAM_TYPE_FM);
+ assertWithMessage("Primary identifiers")
+ .that(selector.getPrimaryId()).isEqualTo(primaryIdExpected);
+ assertWithMessage("Secondary identifiers")
+ .that(selector.getSecondaryIds()).isEmpty();
+ }
+
+ @Test
+ public void createAmFmSelector_withValidFrequencyAndSubChannel() {
+ int band = RadioManager.BAND_AM_HD;
+ int subChannel = 2;
+ ProgramSelector.Identifier primaryIdExpected = new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, AM_FREQUENCY);
+ ProgramSelector.Identifier[] secondaryIdExpected = {
+ new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_HD_SUBCHANNEL, subChannel - 1)
+ };
+
+ ProgramSelector selector = ProgramSelector.createAmFmSelector(band, (int) AM_FREQUENCY,
+ subChannel);
+
+ assertWithMessage("Program type")
+ .that(selector.getProgramType()).isEqualTo(ProgramSelector.PROGRAM_TYPE_AM);
+ assertWithMessage("Primary identifiers")
+ .that(selector.getPrimaryId()).isEqualTo(primaryIdExpected);
+ assertWithMessage("Secondary identifiers")
+ .that(selector.getSecondaryIds()).isEqualTo(secondaryIdExpected);
+ }
+
+ @Test
+ public void createAmFmSelector_withInvalidFrequency_throwsIllegalArgumentException() {
+ int invalidFrequency = 50000;
+
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+ ProgramSelector.createAmFmSelector(RadioManager.BAND_AM, invalidFrequency);
+ });
+
+ assertWithMessage("Exception for using invalid frequency %s", invalidFrequency)
+ .that(thrown).hasMessageThat().contains(
+ "Provided value is not a valid AM/FM frequency: " + invalidFrequency);
+ }
+
+ @Test
+ public void createAmFmSelector_withInvalidSubChannel_throwsIllegalArgumentException() {
+ int invalidSubChannel = 9;
+
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+ ProgramSelector.createAmFmSelector(RadioManager.BAND_FM, (int) FM_FREQUENCY,
+ invalidSubChannel);
+ });
+
+ assertWithMessage("Exception for using invalid subchannel %s", invalidSubChannel)
+ .that(thrown).hasMessageThat().contains(
+ "Invalid subchannel: " + invalidSubChannel);
+ }
+
+ @Test
+ public void createAmFmSelector_withSubChannelNotSupported_throwsIllegalArgumentException() {
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+ ProgramSelector.createAmFmSelector(RadioManager.BAND_FM, (int) FM_FREQUENCY,
+ /* subChannel= */ 1);
+ });
+
+ assertWithMessage("Exception for using sub-channel on radio not supporting it")
+ .that(thrown)
+ .hasMessageThat().contains("Subchannels are not supported for non-HD radio");
+ }
+
+ @Test
+ public void equals_withDifferentSecondaryIds_returnTrue() {
+ ProgramSelector.Identifier[] secondaryIds1 = new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER};
+ ProgramSelector selector1 = getDabSelector(secondaryIds1, /* vendorIds= */ null);
+ ProgramSelector selector2 = getDabSelector(
+ /* secondaryIds= */ null, /* vendorIds= */ null);
+
+ assertWithMessage("Selector with different secondary id")
+ .that(selector1).isEqualTo(selector2);
+ }
+
+ @Test
+ public void equals_withDifferentPrimaryIds_returnFalse() {
+ ProgramSelector selector1 = getFmSelector(
+ /* secondaryIds= */ null, /* vendorIds= */ null);
+ ProgramSelector.Identifier fmIdentifier2 = new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, /* value= */ 88700);
+ ProgramSelector selector2 = new ProgramSelector(FM_PROGRAM_TYPE, fmIdentifier2,
+ /* secondaryIds= */ null, /* vendorIds= */ null);
+
+ assertWithMessage("Selector with different primary id")
+ .that(selector1).isNotEqualTo(selector2);
+ }
+
+ @Test
+ public void strictEquals_withDifferentPrimaryIds_returnsFalse() {
+ ProgramSelector selector1 = getFmSelector(
+ /* secondaryIds= */ null, /* vendorIds= */ null);
+ ProgramSelector.Identifier fmIdentifier2 = new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, /* value= */ 88700);
+ ProgramSelector selector2 = new ProgramSelector(FM_PROGRAM_TYPE, fmIdentifier2,
+ /* secondaryIds= */ null, /* vendorIds= */ null);
+
+ assertWithMessage(
+ "Whether two selectors with different primary ids are strictly equal")
+ .that(selector1.strictEquals(selector2)).isFalse();
+ }
+
+ @Test
+ public void strictEquals_withDifferentSecondaryIds_returnsFalse() {
+ ProgramSelector.Identifier[] secondaryIds1 = new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER};
+ ProgramSelector selector1 = getDabSelector(secondaryIds1, /* vendorIds= */ null);
+ ProgramSelector.Identifier[] secondaryIds2 = new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER};
+ ProgramSelector selector2 = getDabSelector(secondaryIds2, /* vendorIds= */ null);
+
+ assertWithMessage(
+ "Whether two selectors with different secondary ids are strictly equal")
+ .that(selector1.strictEquals(selector2)).isFalse();
+ }
+
+ @Test
+ public void strictEquals_withDifferentSecondaryIdsOrders_returnsTrue() {
+ ProgramSelector.Identifier[] secondaryIds1 = new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER};
+ ProgramSelector selector1 = getDabSelector(secondaryIds1, /* vendorIds= */ null);
+ ProgramSelector.Identifier[] secondaryIds2 = new ProgramSelector.Identifier[]{
+ DAB_FREQUENCY_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER};
+ ProgramSelector selector2 = getDabSelector(secondaryIds2, /* vendorIds= */ null);
+
+ assertWithMessage(
+ "Whether two selectors with different secondary id orders are strictly equal")
+ .that(selector1.strictEquals(selector2)).isTrue();
+ }
+
+ private ProgramSelector getFmSelector(@Nullable ProgramSelector.Identifier[] secondaryIds,
+ @Nullable long[] vendorIds) {
+ return new ProgramSelector(FM_PROGRAM_TYPE, FM_IDENTIFIER, secondaryIds, vendorIds);
+ }
+
+ private ProgramSelector getDabSelector(@Nullable ProgramSelector.Identifier[] secondaryIds,
+ @Nullable long[] vendorIds) {
+ return new ProgramSelector(DAB_PROGRAM_TYPE, DAB_SID_EXT_IDENTIFIER_1, secondaryIds,
+ vendorIds);
+ }
+}
diff --git a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
index daae957..180a312 100644
--- a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
+++ b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
@@ -36,7 +36,6 @@
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.StrictMode;
-import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -48,6 +47,9 @@
import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -57,11 +59,22 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
/**
* Tests for BugreportManager API.
@@ -74,6 +87,7 @@
private static final String TAG = "BugreportManagerTest";
private static final long BUGREPORT_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(10);
private static final long DUMPSTATE_STARTUP_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
+ private static final long DUMPSTATE_TEARDOWN_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
private static final long UIAUTOMATOR_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
@@ -89,6 +103,18 @@
private static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
private static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
+ private static final Path[] UI_TRACES_PREDUMPED = {
+ Paths.get("/data/misc/wmtrace/ime_trace_clients.winscope"),
+ Paths.get("/data/misc/wmtrace/ime_trace_managerservice.winscope"),
+ Paths.get("/data/misc/wmtrace/ime_trace_service.winscope"),
+ Paths.get("/data/misc/wmtrace/wm_trace.winscope"),
+ Paths.get("/data/misc/wmtrace/layers_trace.winscope"),
+ Paths.get("/data/misc/wmtrace/transactions_trace.winscope"),
+ };
+ private static final Path[] UI_TRACES_GENERATED_DURING_BUGREPORT = {
+ Paths.get("/data/misc/wmtrace/layers_trace_from_transactions.winscope"),
+ };
+
private Handler mHandler;
private Executor mExecutor;
private BugreportManager mBrm;
@@ -124,7 +150,6 @@
FileUtils.closeQuietly(mScreenshotFd);
}
-
@Test
public void normalFlow_wifi() throws Exception {
BugreportCallbackImpl callback = new BugreportCallbackImpl();
@@ -175,6 +200,66 @@
assertFdsAreClosed(mBugreportFd, mScreenshotFd);
}
+ @LargeTest
+ @Test
+ public void preDumpUiData_then_fullWithUsePreDumpFlag() throws Exception {
+ startPreDumpedUiTraces();
+
+ mBrm.preDumpUiData();
+ waitTillDumpstateExitedOrTimeout();
+ List<File> expectedPreDumpedTraceFiles = copyFilesAsRoot(UI_TRACES_PREDUMPED);
+
+ BugreportCallbackImpl callback = new BugreportCallbackImpl();
+ mBrm.startBugreport(mBugreportFd, null, fullWithUsePreDumpFlag(), mExecutor,
+ callback);
+ shareConsentDialog(ConsentReply.ALLOW);
+ waitTillDoneOrTimeout(callback);
+
+ stopPreDumpedUiTraces();
+
+ assertThat(callback.isDone()).isTrue();
+ assertThat(mBugreportFile.length()).isGreaterThan(0L);
+ assertFdsAreClosed(mBugreportFd);
+
+ assertThatBugreportContainsFiles(UI_TRACES_PREDUMPED);
+ assertThatBugreportContainsFiles(UI_TRACES_GENERATED_DURING_BUGREPORT);
+
+ List<File> actualPreDumpedTraceFiles = extractFilesFromBugreport(UI_TRACES_PREDUMPED);
+ assertThatAllFileContentsAreEqual(actualPreDumpedTraceFiles, expectedPreDumpedTraceFiles);
+ }
+
+ @LargeTest
+ @Test
+ public void preDumpData_then_fullWithoutUsePreDumpFlag_ignoresPreDump() throws Exception {
+ startPreDumpedUiTraces();
+
+ // Simulate pre-dump, instead of taking a real one.
+ // In some corner cases, data dumped as part of the full bugreport could be the same as the
+ // pre-dumped data and this test would fail. Hence, here we create fake/artificial
+ // pre-dumped data that we know it won't match with the full bugreport data.
+ createFilesWithFakeDataAsRoot(UI_TRACES_PREDUMPED, "system");
+
+ List<File> preDumpedTraceFiles = copyFilesAsRoot(UI_TRACES_PREDUMPED);
+
+ BugreportCallbackImpl callback = new BugreportCallbackImpl();
+ mBrm.startBugreport(mBugreportFd, null, full(), mExecutor,
+ callback);
+ shareConsentDialog(ConsentReply.ALLOW);
+ waitTillDoneOrTimeout(callback);
+
+ stopPreDumpedUiTraces();
+
+ assertThat(callback.isDone()).isTrue();
+ assertThat(mBugreportFile.length()).isGreaterThan(0L);
+ assertFdsAreClosed(mBugreportFd);
+
+ assertThatBugreportContainsFiles(UI_TRACES_PREDUMPED);
+ assertThatBugreportContainsFiles(UI_TRACES_GENERATED_DURING_BUGREPORT);
+
+ List<File> actualTraceFiles = extractFilesFromBugreport(UI_TRACES_PREDUMPED);
+ assertThatAllFileContentsAreDifferent(preDumpedTraceFiles, actualTraceFiles);
+ }
+
@Test
public void simultaneousBugreportsNotAllowed() throws Exception {
// Start bugreport #1
@@ -384,21 +469,151 @@
}
return bm;
}
-
private static File createTempFile(String prefix, String extension) throws Exception {
final File f = File.createTempFile(prefix, extension);
f.setReadable(true, true);
f.setWritable(true, true);
-
f.deleteOnExit();
return f;
}
+ private static void startPreDumpedUiTraces() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "cmd input_method tracing start"
+ );
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "cmd window tracing start"
+ );
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "service call SurfaceFlinger 1025 i32 1"
+ );
+ }
+
+ private static void stopPreDumpedUiTraces() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "cmd input_method tracing stop"
+ );
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "cmd window tracing stop"
+ );
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "service call SurfaceFlinger 1025 i32 0"
+ );
+ }
+
+ private void assertThatBugreportContainsFiles(Path[] paths)
+ throws IOException {
+ List<Path> entries = listZipArchiveEntries(mBugreportFile);
+ for (Path pathInDevice : paths) {
+ Path pathInArchive = Paths.get("FS" + pathInDevice.toString());
+ assertThat(entries).contains(pathInArchive);
+ }
+ }
+
+ private List<File> extractFilesFromBugreport(Path[] paths) throws Exception {
+ List<File> files = new ArrayList<File>();
+ for (Path pathInDevice : paths) {
+ Path pathInArchive = Paths.get("FS" + pathInDevice.toString());
+ files.add(extractZipArchiveEntry(mBugreportFile, pathInArchive));
+ }
+ return files;
+ }
+
+ private static List<Path> listZipArchiveEntries(File archive) throws IOException {
+ ArrayList<Path> entries = new ArrayList<>();
+
+ ZipInputStream stream = new ZipInputStream(
+ new BufferedInputStream(new FileInputStream(archive)));
+
+ for (ZipEntry entry = stream.getNextEntry(); entry != null; entry = stream.getNextEntry()) {
+ entries.add(Paths.get(entry.toString()));
+ }
+
+ return entries;
+ }
+
+ private static File extractZipArchiveEntry(File archive, Path entryToExtract)
+ throws Exception {
+ File extractedFile = createTempFile(entryToExtract.getFileName().toString(), ".extracted");
+
+ ZipInputStream is = new ZipInputStream(new FileInputStream(archive));
+ boolean hasFoundEntry = false;
+
+ for (ZipEntry entry = is.getNextEntry(); entry != null; entry = is.getNextEntry()) {
+ if (entry.toString().equals(entryToExtract.toString())) {
+ BufferedOutputStream os =
+ new BufferedOutputStream(new FileOutputStream(extractedFile));
+ ByteStreams.copy(is, os);
+ os.close();
+ hasFoundEntry = true;
+ break;
+ }
+
+ ByteStreams.exhaust(is); // skip entry
+ }
+
+ is.closeEntry();
+ is.close();
+
+ assertThat(hasFoundEntry).isTrue();
+
+ return extractedFile;
+ }
+
+ private static void createFilesWithFakeDataAsRoot(Path[] paths, String owner) throws Exception {
+ File src = createTempFile("fake", ".data");
+ Files.write("fake data".getBytes(StandardCharsets.UTF_8), src);
+
+ for (Path path : paths) {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "install -m 611 -o " + owner + " -g " + owner
+ + " " + src.getAbsolutePath() + " " + path.toString()
+ );
+ }
+ }
+
+ private static List<File> copyFilesAsRoot(Path[] paths) throws Exception {
+ ArrayList<File> files = new ArrayList<File>();
+ for (Path src : paths) {
+ File dst = createTempFile(src.getFileName().toString(), ".copy");
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "cp " + src.toString() + " " + dst.getAbsolutePath()
+ );
+ files.add(dst);
+ }
+ return files;
+ }
+
private static ParcelFileDescriptor parcelFd(File file) throws Exception {
return ParcelFileDescriptor.open(file,
ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_APPEND);
}
+ private static void assertThatAllFileContentsAreEqual(List<File> actual, List<File> expected)
+ throws IOException {
+ if (actual.size() != expected.size()) {
+ fail("File lists have different size");
+ }
+ for (int i = 0; i < actual.size(); ++i) {
+ if (!Files.equal(actual.get(i), expected.get(i))) {
+ fail("Contents of " + actual.get(i).toString()
+ + " != " + expected.get(i).toString());
+ }
+ }
+ }
+
+ private static void assertThatAllFileContentsAreDifferent(List<File> a, List<File> b)
+ throws IOException {
+ if (a.size() != b.size()) {
+ fail("File lists have different size");
+ }
+ for (int i = 0; i < a.size(); ++i) {
+ if (Files.equal(a.get(i), b.get(i))) {
+ fail("Contents of " + a.get(i).toString() + " == " + b.get(i).toString());
+ }
+ }
+ }
+
private static void dropPermissions() {
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.dropShellPermissionIdentity();
@@ -410,21 +625,16 @@
}
private static boolean isDumpstateRunning() {
- String[] output;
+ String output;
try {
- output =
- UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
- .executeShellCommand("ps -A -o NAME | grep dumpstate")
- .trim()
- .split("\n");
+ output = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+ .executeShellCommand("service list | grep dumpstate");
} catch (IOException e) {
Log.w(TAG, "Failed to check if dumpstate is running", e);
return false;
}
- for (String line : output) {
- // Check for an exact match since there may be other things that contain "dumpstate" as
- // a substring (e.g. the dumpstate HAL).
- if (TextUtils.equals("dumpstate", line)) {
+ for (String line : output.trim().split("\n")) {
+ if (line.matches("^.*\\s+dumpstate:\\s+\\[.*\\]$")) {
return true;
}
}
@@ -449,6 +659,17 @@
return System.currentTimeMillis();
}
+ private static void waitTillDumpstateExitedOrTimeout() throws Exception {
+ long startTimeMs = now();
+ while (isDumpstateRunning()) {
+ Thread.sleep(500 /* .5s */);
+ if (now() - startTimeMs >= DUMPSTATE_TEARDOWN_TIMEOUT_MS) {
+ break;
+ }
+ Log.d(TAG, "Waited " + (now() - startTimeMs) + "ms for dumpstate to exit");
+ }
+ }
+
private static void waitTillDumpstateRunningOrTimeout() throws Exception {
long startTimeMs = now();
while (!isDumpstateRunning()) {
@@ -500,6 +721,16 @@
return new BugreportParams(BugreportParams.BUGREPORT_MODE_FULL);
}
+ /*
+ * Returns a {@link BugreportParams} for full bugreport that reuses pre-dumped data.
+ *
+ * <p> This can take on the order of minutes to finish
+ */
+ private static BugreportParams fullWithUsePreDumpFlag() {
+ return new BugreportParams(BugreportParams.BUGREPORT_MODE_FULL,
+ BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA);
+ }
+
/* Allow/deny the consent dialog to sharing bugreport data or check existence only. */
private enum ConsentReply {
ALLOW,
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index fb0a9db..7e9c418 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -41,7 +41,7 @@
// TODO(b/241126279) Introduce constants to better version functionality
@Override
public int getVendorApiLevel() {
- return 1;
+ return 2;
}
/**
diff --git a/packages/SettingsLib/Spa/TEST_MAPPING b/packages/SettingsLib/Spa/TEST_MAPPING
index b4b65d4..b7ce518 100644
--- a/packages/SettingsLib/Spa/TEST_MAPPING
+++ b/packages/SettingsLib/Spa/TEST_MAPPING
@@ -5,6 +5,9 @@
},
{
"name": "SpaPrivilegedLibTests"
+ },
+ {
+ "name": "SettingsSpaUnitTests"
}
]
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index aa457fe..d154dc1 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -16,11 +16,9 @@
package com.android.settingslib.spa.gallery
-import android.os.Bundle
-import androidx.navigation.NamedNavArgument
-import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
import com.android.settingslib.spa.framework.common.SpaEnvironment
+import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
import com.android.settingslib.spa.gallery.home.HomePageProvider
import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
@@ -49,19 +47,6 @@
// Add your SPPs
}
-fun createSettingsPage(
- SppName: SettingsPageProviderEnum,
- parameter: List<NamedNavArgument> = emptyList(),
- arguments: Bundle? = null
-): SettingsPage {
- return SettingsPage.create(
- name = SppName.name,
- displayName = SppName.displayName,
- parameter = parameter,
- arguments = arguments,
- )
-}
-
object GallerySpaEnvironment : SpaEnvironment() {
override val pageProviderRepository = lazy {
SettingsPageProviderRepository(
@@ -82,7 +67,7 @@
ActionButtonPageProvider,
),
rootPages = listOf(
- createSettingsPage(SettingsPageProviderEnum.HOME)
+ HomePageProvider.createSettingsPage(),
)
)
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
index 33cd5f1..e40775a 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
@@ -22,11 +22,11 @@
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.gallery.R
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
-import com.android.settingslib.spa.gallery.createSettingsPage
import com.android.settingslib.spa.gallery.page.ArgumentPageModel
import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
import com.android.settingslib.spa.gallery.page.FooterPageProvider
@@ -40,9 +40,10 @@
object HomePageProvider : SettingsPageProvider {
override val name = SettingsPageProviderEnum.HOME.name
+ override val displayName = SettingsPageProviderEnum.HOME.displayName
+ private val owner = createSettingsPage()
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- val owner = createSettingsPage(SettingsPageProviderEnum.HOME)
return listOf(
PreferenceMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
ArgumentPageProvider.buildInjectEntry("foo")!!.setLink(fromPage = owner).build(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
index 5031fb4..8207310 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
@@ -23,9 +23,9 @@
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
-import com.android.settingslib.spa.gallery.createSettingsPage
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
@@ -43,13 +43,13 @@
}
override val name = SettingsPageProviderEnum.ARGUMENT.name
-
+ override val displayName = SettingsPageProviderEnum.ARGUMENT.displayName
override val parameter = ArgumentPageModel.parameter
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
if (!ArgumentPageModel.isValidArgument(arguments)) return emptyList()
- val owner = createSettingsPage(SettingsPageProviderEnum.ARGUMENT, parameter, arguments)
+ val owner = createSettingsPage(arguments)
val entryList = mutableListOf<SettingsEntry>()
entryList.add(
createEntry(owner, EntryEnum.STRING_PARAM)
@@ -86,7 +86,7 @@
if (!ArgumentPageModel.isValidArgument(arguments)) return null
return SettingsEntryBuilder.createInject(
- owner = createSettingsPage(SettingsPageProviderEnum.ARGUMENT, parameter, arguments),
+ owner = createSettingsPage(arguments),
displayName = "${name}_$stringParam",
)
// Set attributes
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt
index 0f99c57..165eaa0 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt
@@ -20,8 +20,8 @@
import androidx.compose.runtime.Composable
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
@@ -31,22 +31,20 @@
object PreferenceMainPageProvider : SettingsPageProvider {
override val name = "PreferenceMain"
+ private val owner = createSettingsPage()
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
return listOf(
- PreferencePageProvider.buildInjectEntry()
- .setLink(fromPage = SettingsPage.create(name)).build(),
- SwitchPreferencePageProvider.buildInjectEntry()
- .setLink(fromPage = SettingsPage.create(name)).build(),
- MainSwitchPreferencePageProvider.buildInjectEntry()
- .setLink(fromPage = SettingsPage.create(name)).build(),
+ PreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ SwitchPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ MainSwitchPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
TwoTargetSwitchPreferencePageProvider.buildInjectEntry()
- .setLink(fromPage = SettingsPage.create(name)).build(),
+ .setLink(fromPage = owner).build(),
)
}
fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+ return SettingsEntryBuilder.createInject(owner = owner)
.setIsAllowSearch(true)
.setUiLayoutFn {
Preference(object : PreferenceModel {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
index f7f01ea..f19e9a3 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
@@ -30,11 +30,11 @@
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.gallery.R
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
-import com.android.settingslib.spa.gallery.createSettingsPage
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_TITLE
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.AUTO_UPDATE_PREFERENCE_TITLE
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.DISABLE_PREFERENCE_SUMMARY
@@ -66,7 +66,8 @@
}
override val name = SettingsPageProviderEnum.PREFERENCE.name
- private val owner = createSettingsPage(SettingsPageProviderEnum.PREFERENCE)
+ override val displayName = SettingsPageProviderEnum.PREFERENCE.displayName
+ private val owner = createSettingsPage()
private fun createEntry(entry: EntryEnum): SettingsEntryBuilder {
return SettingsEntryBuilder.create(owner, entry.name, entry.displayName)
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp
index 1d42e27..8b29366 100644
--- a/packages/SettingsLib/Spa/spa/Android.bp
+++ b/packages/SettingsLib/Spa/spa/Android.bp
@@ -29,6 +29,7 @@
"androidx.compose.runtime_runtime",
"androidx.compose.runtime_runtime-livedata",
"androidx.compose.ui_ui-tooling-preview",
+ "androidx.lifecycle_lifecycle-livedata-ktx",
"androidx.navigation_navigation-compose",
"com.google.android.material_material",
"lottie_compose",
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index 362953f..7e05e75 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -59,13 +59,14 @@
}
dependencies {
- api "androidx.appcompat:appcompat:1.6.0-rc01"
+ api "androidx.appcompat:appcompat:1.7.0-alpha01"
api "androidx.compose.material3:material3:$jetpack_compose_material3_version"
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.navigation:navigation-compose:2.5.0'
- api 'com.google.android.material:material:1.6.1'
+ api "androidx.lifecycle:lifecycle-livedata-ktx:2.6.0-alpha02"
+ api "androidx.navigation:navigation-compose:2.5.0"
+ api "com.google.android.material:material:1.6.1"
debugApi "androidx.compose.ui:ui-tooling:$jetpack_compose_version"
- implementation 'com.airbnb.android:lottie-compose:5.2.0'
+ implementation "com.airbnb.android:lottie-compose:5.2.0"
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
index ed4004f2..ea20233 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
@@ -55,7 +55,7 @@
val entry = entryQueue.pop()
val page = entry.toPage
if (page == null || pageWithEntryMap.containsKey(page.id)) continue
- val spp = sppRepository.getProviderOrNull(page.name) ?: continue
+ val spp = sppRepository.getProviderOrNull(page.sppName) ?: continue
val newEntries = spp.buildEntry(page.arguments)
pageWithEntryMap[page.id] = SettingsPageWithEntry(page, newEntries)
for (newEntry in newEntries) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index 2659c10..e7d8906 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -26,16 +26,16 @@
* Defines data to identify a Settings page.
*/
data class SettingsPage(
- // The unique id of this page, which is computed by name + normalized(arguments)
+ // The unique id of this page, which is computed by sppName + normalized(arguments)
val id: String,
- // The name of the page, which is used to compute the unique id, and need to be stable.
- val name: String,
+ // The name of the page provider, who creates this page. It is used to compute the unique id.
+ val sppName: String,
// The display name of the page, for better readability.
val displayName: String,
- // Defined parameters of this page.
+ // The parameters defined in its page provider.
val parameter: List<NamedNavArgument> = emptyList(),
// The arguments of this page.
@@ -50,7 +50,7 @@
): SettingsPage {
return SettingsPage(
id = id(name, parameter, arguments),
- name = name,
+ sppName = name,
displayName = displayName ?: name,
parameter = parameter,
arguments = arguments
@@ -70,7 +70,7 @@
// Returns if this Settings Page is created by the given Spp.
fun isCreateBy(SppName: String): Boolean {
- return name == SppName
+ return sppName == SppName
}
fun formatArguments(): String {
@@ -84,7 +84,7 @@
}
fun buildRoute(): String {
- return name + parameter.navLink(arguments)
+ return sppName + parameter.navLink(arguments)
}
fun hasRuntimeParam(): Boolean {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
index 965c2b3..e8a4411 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
@@ -21,13 +21,17 @@
import androidx.navigation.NamedNavArgument
/**
- * An SettingsPageProvider represent a Settings page.
+ * An SettingsPageProvider which is used to create Settings page instances.
*/
interface SettingsPageProvider {
- /** The page name without arguments. */
+ /** The page provider name, needs to be *unique* and *stable*. */
val name: String
+ /** The display name of this page provider, for better readability. */
+ val displayName: String?
+ get() = null
+
/** The page parameters, default is no parameters. */
val parameter: List<NamedNavArgument>
get() = emptyList()
@@ -38,3 +42,12 @@
fun buildEntry(arguments: Bundle?): List<SettingsEntry> = emptyList()
}
+
+fun SettingsPageProvider.createSettingsPage(arguments: Bundle? = null): SettingsPage {
+ return SettingsPage.create(
+ name = name,
+ displayName = displayName,
+ parameter = parameter,
+ arguments = arguments
+ )
+}
diff --git a/packages/SettingsLib/SpaPrivileged/TEST_MAPPING b/packages/SettingsLib/SpaPrivileged/TEST_MAPPING
new file mode 100644
index 0000000..ea16682
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/TEST_MAPPING
@@ -0,0 +1,10 @@
+{
+ "presubmit": [
+ {
+ "name": "SpaPrivilegedLibTests"
+ },
+ {
+ "name": "SettingsSpaUnitTests"
+ }
+ ]
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
index 93ba4f7..71cf23c 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
@@ -24,7 +24,7 @@
import android.content.pm.ApplicationInfo
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.Transformations
+import androidx.lifecycle.map
class AppOpsController(
context: Context,
@@ -36,7 +36,7 @@
val mode: LiveData<Int>
get() = _mode
val isAllowed: LiveData<Boolean>
- get() = Transformations.map(_mode) { it == MODE_ALLOWED }
+ get() = _mode.map { it == MODE_ALLOWED }
fun setAllowed(allowed: Boolean) {
val mode = if (allowed) MODE_ALLOWED else MODE_ERRORED
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
index 0615807..b1adc9d 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
@@ -20,7 +20,7 @@
import android.content.Context
import android.os.UserHandle
import android.os.UserManager
-import androidx.lifecycle.LiveData
+import androidx.lifecycle.liveData
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
import com.android.settingslib.RestrictedLockUtilsInternal
import com.android.settingslib.spaprivileged.R
@@ -58,13 +58,8 @@
private val userManager by lazy { UserManager.get(context) }
private val enterpriseRepository by lazy { EnterpriseRepository(context) }
- val restrictedMode = object : LiveData<RestrictedMode>() {
- override fun onActive() {
- postValue(getRestrictedMode())
- }
-
- override fun onInactive() {
- }
+ val restrictedMode = liveData {
+ emit(getRestrictedMode())
}
private fun getRestrictedMode(): RestrictedMode {
diff --git a/packages/SettingsLib/SpaPrivileged/tests/Android.bp b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
index 940a1fe..a1222a1 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
@@ -41,6 +41,6 @@
],
kotlincflags: [
"-Xjvm-default=all",
- "-Xopt-in=kotlin.RequiresOptIn",
+ "-opt-in=kotlin.RequiresOptIn",
],
}
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index 9476912..cb37c07 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -199,8 +199,8 @@
}
mBugreportFd = ParcelFileDescriptor.dup(invocation.getArgument(2));
return null;
- }).when(mMockIDumpstate).startBugreport(anyInt(), any(), any(), any(), anyInt(), any(),
- anyBoolean());
+ }).when(mMockIDumpstate).startBugreport(anyInt(), any(), any(), any(), anyInt(), anyInt(),
+ any(), anyBoolean());
setWarningState(mContext, STATE_HIDE);
@@ -543,7 +543,7 @@
getInstrumentation().waitForIdleSync();
verify(mMockIDumpstate, times(1)).startBugreport(anyInt(), any(), any(), any(),
- anyInt(), any(), anyBoolean());
+ anyInt(), anyInt(), any(), anyBoolean());
sendBugreportFinished();
}
@@ -608,7 +608,7 @@
ArgumentCaptor<IDumpstateListener> listenerCap = ArgumentCaptor.forClass(
IDumpstateListener.class);
verify(mMockIDumpstate, timeout(TIMEOUT)).startBugreport(anyInt(), any(), any(), any(),
- anyInt(), listenerCap.capture(), anyBoolean());
+ anyInt(), anyInt(), listenerCap.capture(), anyBoolean());
mIDumpstateListener = listenerCap.getValue();
assertNotNull("Dumpstate listener should not be null", mIDumpstateListener);
mIDumpstateListener.onProgress(0);
diff --git a/services/core/java/com/android/server/attention/TEST_MAPPING b/services/core/java/com/android/server/attention/TEST_MAPPING
index 35b8165..e5b0344 100644
--- a/services/core/java/com/android/server/attention/TEST_MAPPING
+++ b/services/core/java/com/android/server/attention/TEST_MAPPING
@@ -7,7 +7,7 @@
"include-filter": "android.voiceinteraction.cts.AlwaysOnHotwordDetectorTest"
},
{
- "include-filter": "android.voiceinteraction.cts.HotwordDetectedResultTest"
+ "include-filter": "android.voiceinteraction.cts.unittests.HotwordDetectedResultTest"
},
{
"include-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest"
diff --git a/services/core/java/com/android/server/biometrics/log/ALSProbe.java b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
index 62f94ed..1a5f31c 100644
--- a/services/core/java/com/android/server/biometrics/log/ALSProbe.java
+++ b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
@@ -30,7 +30,10 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.sensors.BaseClientMonitor;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
/** Probe for ambient light. */
final class ALSProbe implements Probe {
@@ -47,12 +50,18 @@
private boolean mEnabled = false;
private boolean mDestroyed = false;
+ private boolean mDestroyRequested = false;
+ private boolean mDisableRequested = false;
+ private volatile NextConsumer mNextConsumer = null;
private volatile float mLastAmbientLux = -1;
private final SensorEventListener mLightSensorListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
mLastAmbientLux = event.values[0];
+ if (mNextConsumer != null) {
+ completeNextConsumer(mLastAmbientLux);
+ }
}
@Override
@@ -102,29 +111,84 @@
@Override
public synchronized void enable() {
- if (!mDestroyed) {
+ if (!mDestroyed && !mDestroyRequested) {
+ mDisableRequested = false;
enableLightSensorLoggingLocked();
}
}
@Override
public synchronized void disable() {
- if (!mDestroyed) {
+ mDisableRequested = true;
+
+ // if a final consumer is set it will call destroy/disable on the next value if requested
+ if (!mDestroyed && mNextConsumer == null) {
disableLightSensorLoggingLocked();
}
}
@Override
public synchronized void destroy() {
- disable();
- mDestroyed = true;
+ mDestroyRequested = true;
+
+ // if a final consumer is set it will call destroy/disable on the next value if requested
+ if (!mDestroyed && mNextConsumer == null) {
+ disable();
+ mDestroyed = true;
+ }
}
/** The most recent lux reading. */
- public float getCurrentLux() {
+ public float getMostRecentLux() {
return mLastAmbientLux;
}
+ /**
+ * Register a listener for the next available ALS reading, which will be reported to the given
+ * consumer even if this probe is {@link #disable()}'ed or {@link #destroy()}'ed before a value
+ * is available.
+ *
+ * This method is intended to be used for event logs that occur when the screen may be
+ * off and sampling may have been {@link #disable()}'ed. In these cases, this method will turn
+ * on the sensor (if needed), fetch & report the first value, and then destroy or disable this
+ * probe (if needed).
+ *
+ * @param consumer consumer to notify when the data is available
+ * @param handler handler for notifying the consumer, or null
+ */
+ public synchronized void awaitNextLux(@NonNull Consumer<Float> consumer,
+ @Nullable Handler handler) {
+ final NextConsumer nextConsumer = new NextConsumer(consumer, handler);
+ final float current = mLastAmbientLux;
+ if (current > 0) {
+ nextConsumer.consume(current);
+ } else if (mDestroyed) {
+ nextConsumer.consume(-1f);
+ } else if (mNextConsumer != null) {
+ mNextConsumer.add(nextConsumer);
+ } else {
+ mNextConsumer = nextConsumer;
+ enableLightSensorLoggingLocked();
+ }
+ }
+
+ private synchronized void completeNextConsumer(float value) {
+ Slog.v(TAG, "Finishing next consumer");
+
+ final NextConsumer consumer = mNextConsumer;
+ mNextConsumer = null;
+
+ if (mDestroyRequested) {
+ destroy();
+ } else if (mDisableRequested) {
+ disable();
+ }
+
+ if (consumer != null) {
+ consumer.consume(value);
+ }
+ }
+
private void enableLightSensorLoggingLocked() {
if (!mEnabled) {
mEnabled = true;
@@ -160,4 +224,30 @@
+ mLightSensorListener.hashCode());
disable();
}
+
+ private static class NextConsumer {
+ @NonNull private final Consumer<Float> mConsumer;
+ @Nullable private final Handler mHandler;
+ @NonNull private final List<NextConsumer> mOthers = new ArrayList<>();
+
+ private NextConsumer(@NonNull Consumer<Float> consumer, @Nullable Handler handler) {
+ mConsumer = consumer;
+ mHandler = handler;
+ }
+
+ public void consume(float value) {
+ if (mHandler != null) {
+ mHandler.post(() -> mConsumer.accept(value));
+ } else {
+ mConsumer.accept(value);
+ }
+ for (NextConsumer c : mOthers) {
+ c.consume(value);
+ }
+ }
+
+ public void add(NextConsumer consumer) {
+ mOthers.add(consumer);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
index d6ca8a6..27a70c5 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
@@ -62,8 +62,7 @@
/** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */
public void authenticate(OperationContext operationContext,
int statsModality, int statsAction, int statsClient, boolean isDebug, long latency,
- int authState, boolean requireConfirmation,
- int targetUserId, float ambientLightLux) {
+ int authState, boolean requireConfirmation, int targetUserId, float ambientLightLux) {
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED,
statsModality,
targetUserId,
@@ -80,6 +79,16 @@
operationContext.isAod);
}
+ /** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */
+ public void authenticate(OperationContext operationContext,
+ int statsModality, int statsAction, int statsClient, boolean isDebug, long latency,
+ int authState, boolean requireConfirmation, int targetUserId, ALSProbe alsProbe) {
+ alsProbe.awaitNextLux((ambientLightLux) -> {
+ authenticate(operationContext, statsModality, statsAction, statsClient, isDebug,
+ latency, authState, requireConfirmation, targetUserId, ambientLightLux);
+ }, null /* handler */);
+ }
+
/** {@see FrameworkStatsLog.BIOMETRIC_ENROLLED}. */
public void enroll(int statsModality, int statsAction, int statsClient,
int targetUserId, long latency, boolean enrollSuccessful, float ambientLightLux) {
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
index 02b350e..55fe854 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
@@ -220,7 +220,7 @@
+ ", RequireConfirmation: " + requireConfirmation
+ ", State: " + authState
+ ", Latency: " + latency
- + ", Lux: " + mALSProbe.getCurrentLux());
+ + ", Lux: " + mALSProbe.getMostRecentLux());
} else {
Slog.v(TAG, "Authentication latency: " + latency);
}
@@ -231,7 +231,7 @@
mSink.authenticate(operationContext, mStatsModality, mStatsAction, mStatsClient,
Utils.isDebugEnabled(context, targetUserId),
- latency, authState, requireConfirmation, targetUserId, mALSProbe.getCurrentLux());
+ latency, authState, requireConfirmation, targetUserId, mALSProbe);
}
/** Log enrollment outcome. */
@@ -245,7 +245,7 @@
+ ", User: " + targetUserId
+ ", Client: " + mStatsClient
+ ", Latency: " + latency
- + ", Lux: " + mALSProbe.getCurrentLux()
+ + ", Lux: " + mALSProbe.getMostRecentLux()
+ ", Success: " + enrollSuccessful);
} else {
Slog.v(TAG, "Enroll latency: " + latency);
@@ -256,7 +256,7 @@
}
mSink.enroll(mStatsModality, mStatsAction, mStatsClient,
- targetUserId, latency, enrollSuccessful, mALSProbe.getCurrentLux());
+ targetUserId, latency, enrollSuccessful, mALSProbe.getMostRecentLux());
}
/** Report unexpected enrollment reported by the HAL. */
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 e0955b7..5e6a025 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
@@ -333,6 +333,9 @@
mALSProbeCallback.getProbe().disable();
}
});
+ if (getBiometricContext().isAwake()) {
+ mALSProbeCallback.getProbe().enable();
+ }
if (session.hasContextMethods()) {
return session.getSession().authenticateWithContext(mOperationId, opContext);
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 346f311..58428ca 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -43,6 +43,7 @@
import java.io.FileDescriptor;
import java.util.Objects;
+import java.util.OptionalInt;
/**
* Implementation of the service that provides a privileged API to capture and consume bugreports.
@@ -60,6 +61,9 @@
private final TelephonyManager mTelephonyManager;
private final ArraySet<String> mBugreportWhitelistedPackages;
+ @GuardedBy("mLock")
+ private OptionalInt mPreDumpedDataUid = OptionalInt.empty();
+
BugreportManagerServiceImpl(Context context) {
mContext = context;
mAppOps = context.getSystemService(AppOpsManager.class);
@@ -70,13 +74,25 @@
@Override
@RequiresPermission(android.Manifest.permission.DUMP)
+ public void preDumpUiData(String callingPackage) {
+ enforcePermission(callingPackage, Binder.getCallingUid(), true);
+
+ synchronized (mLock) {
+ preDumpUiDataLocked(callingPackage);
+ }
+ }
+
+ @Override
+ @RequiresPermission(android.Manifest.permission.DUMP)
public void startBugreport(int callingUidUnused, String callingPackage,
FileDescriptor bugreportFd, FileDescriptor screenshotFd,
- int bugreportMode, IDumpstateListener listener, boolean isScreenshotRequested) {
+ int bugreportMode, int bugreportFlags, IDumpstateListener listener,
+ boolean isScreenshotRequested) {
Objects.requireNonNull(callingPackage);
Objects.requireNonNull(bugreportFd);
Objects.requireNonNull(listener);
validateBugreportMode(bugreportMode);
+ validateBugreportFlags(bugreportFlags);
int callingUid = Binder.getCallingUid();
enforcePermission(callingPackage, callingUid, bugreportMode
@@ -90,7 +106,7 @@
synchronized (mLock) {
startBugreportLocked(callingUid, callingPackage, bugreportFd, screenshotFd,
- bugreportMode, listener, isScreenshotRequested);
+ bugreportMode, bugreportFlags, listener, isScreenshotRequested);
}
}
@@ -108,17 +124,14 @@
}
try {
// Note: this may throw SecurityException back out to the caller if they aren't
- // allowed to cancel the report, in which case we should NOT be setting ctl.stop,
- // since that would unintentionally kill some other app's bugreport, which we
- // specifically disallow.
+ // allowed to cancel the report, in which case we should NOT stop the dumpstate
+ // service, since that would unintentionally kill some other app's bugreport, which
+ // we specifically disallow.
ds.cancelBugreport(callingUid, callingPackage);
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException in cancelBugreport", e);
}
- // This tells init to cancel bugreportd service. Note that this is achieved through
- // setting a system property which is not thread-safe. So the lock here offers
- // thread-safety only among callers of the API.
- SystemProperties.set("ctl.stop", BUGREPORT_SERVICE);
+ stopDumpstateBinderServiceLocked();
}
}
@@ -134,6 +147,14 @@
}
}
+ private void validateBugreportFlags(int flags) {
+ flags = clearBugreportFlag(flags, BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA);
+ if (flags != 0) {
+ Slog.w(TAG, "Unknown bugreport flags: " + flags);
+ throw new IllegalArgumentException("Unknown bugreport flags: " + flags);
+ }
+ }
+
private void enforcePermission(
String callingPackage, int callingUid, boolean checkCarrierPrivileges) {
mAppOps.checkPackage(callingUid, callingPackage);
@@ -224,17 +245,62 @@
}
@GuardedBy("mLock")
+ private void preDumpUiDataLocked(String callingPackage) {
+ mPreDumpedDataUid = OptionalInt.empty();
+
+ if (isDumpstateBinderServiceRunningLocked()) {
+ Slog.e(TAG, "'dumpstate' is already running. "
+ + "Cannot pre-dump data while another operation is currently in progress.");
+ return;
+ }
+
+ IDumpstate ds = startAndGetDumpstateBinderServiceLocked();
+ if (ds == null) {
+ Slog.e(TAG, "Unable to get bugreport service");
+ return;
+ }
+
+ try {
+ ds.preDumpUiData(callingPackage);
+ } catch (RemoteException e) {
+ return;
+ } finally {
+ // dumpstate service is already started now. We need to kill it to manage the
+ // lifecycle correctly. If we don't subsequent callers will get
+ // BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS error.
+ stopDumpstateBinderServiceLocked();
+ }
+
+
+ mPreDumpedDataUid = OptionalInt.of(Binder.getCallingUid());
+ }
+
+ @GuardedBy("mLock")
private void startBugreportLocked(int callingUid, String callingPackage,
FileDescriptor bugreportFd, FileDescriptor screenshotFd,
- int bugreportMode, IDumpstateListener listener, boolean isScreenshotRequested) {
+ int bugreportMode, int bugreportFlags, IDumpstateListener listener,
+ boolean isScreenshotRequested) {
if (isDumpstateBinderServiceRunningLocked()) {
Slog.w(TAG, "'dumpstate' is already running. Cannot start a new bugreport"
- + " while another one is currently in progress.");
- reportError(listener,
- IDumpstateListener.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS);
+ + " while another operation is currently in progress.");
+ reportError(listener, IDumpstateListener.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS);
return;
}
+ if ((bugreportFlags & BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA) != 0) {
+ if (mPreDumpedDataUid.isEmpty()) {
+ bugreportFlags = clearBugreportFlag(bugreportFlags,
+ BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA);
+ Slog.w(TAG, "Ignoring BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA."
+ + " No pre-dumped data is available.");
+ } else if (mPreDumpedDataUid.getAsInt() != callingUid) {
+ bugreportFlags = clearBugreportFlag(bugreportFlags,
+ BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA);
+ Slog.w(TAG, "Ignoring BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA."
+ + " Data was pre-dumped by a different UID.");
+ }
+ }
+
IDumpstate ds = startAndGetDumpstateBinderServiceLocked();
if (ds == null) {
Slog.w(TAG, "Unable to get bugreport service");
@@ -245,10 +311,10 @@
// Wrap the listener so we can intercept binder events directly.
IDumpstateListener myListener = new DumpstateListener(listener, ds);
try {
- ds.startBugreport(callingUid, callingPackage,
- bugreportFd, screenshotFd, bugreportMode, myListener, isScreenshotRequested);
+ ds.startBugreport(callingUid, callingPackage, bugreportFd, screenshotFd, bugreportMode,
+ bugreportFlags, myListener, isScreenshotRequested);
} catch (RemoteException e) {
- // bugreportd service is already started now. We need to kill it to manage the
+ // dumpstate service is already started now. We need to kill it to manage the
// lifecycle correctly. If we don't subsequent callers will get
// BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS error.
// Note that listener will be notified by the death recipient below.
@@ -309,6 +375,19 @@
return ds;
}
+ @GuardedBy("mLock")
+ private void stopDumpstateBinderServiceLocked() {
+ // This tells init to cancel bugreportd service. Note that this is achieved through
+ // setting a system property which is not thread-safe. So the lock here offers
+ // thread-safety only among callers of the API.
+ SystemProperties.set("ctl.stop", BUGREPORT_SERVICE);
+ }
+
+ private int clearBugreportFlag(int flags, @BugreportParams.BugreportFlag int flag) {
+ flags &= ~flag;
+ return flags;
+ }
+
private void reportError(IDumpstateListener listener, int errorCode) {
try {
listener.onError(errorCode);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
index 10f0a5c..68c9ce4 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
@@ -23,6 +23,7 @@
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@@ -50,6 +51,9 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
@Presubmit
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -93,7 +97,7 @@
mSensorEventListenerCaptor.getValue().onSensorChanged(
new SensorEvent(mLightSensor, 1, 2, new float[]{value}));
- assertThat(mProbe.getCurrentLux()).isEqualTo(value);
+ assertThat(mProbe.getMostRecentLux()).isEqualTo(value);
}
@Test
@@ -121,13 +125,17 @@
mProbe.destroy();
mProbe.enable();
+ AtomicInteger lux = new AtomicInteger(10);
+ mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
+
verify(mSensorManager, never()).registerListener(any(), any(), anyInt());
verifyNoMoreInteractions(mSensorManager);
+ assertThat(lux.get()).isLessThan(0);
}
@Test
public void testDisabledReportsNegativeValue() {
- assertThat(mProbe.getCurrentLux()).isLessThan(0f);
+ assertThat(mProbe.getMostRecentLux()).isLessThan(0f);
mProbe.enable();
verify(mSensorManager).registerListener(
@@ -136,7 +144,7 @@
new SensorEvent(mLightSensor, 1, 1, new float[]{4.0f}));
mProbe.disable();
- assertThat(mProbe.getCurrentLux()).isLessThan(0f);
+ assertThat(mProbe.getMostRecentLux()).isLessThan(0f);
}
@Test
@@ -150,7 +158,7 @@
verify(mSensorManager).unregisterListener(eq(mSensorEventListenerCaptor.getValue()));
verifyNoMoreInteractions(mSensorManager);
- assertThat(mProbe.getCurrentLux()).isLessThan(0f);
+ assertThat(mProbe.getMostRecentLux()).isLessThan(0f);
}
@Test
@@ -166,7 +174,148 @@
verify(mSensorManager).unregisterListener(any(SensorEventListener.class));
verifyNoMoreInteractions(mSensorManager);
- assertThat(mProbe.getCurrentLux()).isLessThan(0f);
+ assertThat(mProbe.getMostRecentLux()).isLessThan(0f);
+ }
+
+ @Test
+ public void testNextLuxWhenAlreadyEnabledAndNotAvailable() {
+ testNextLuxWhenAlreadyEnabled(false /* dataIsAvailable */);
+ }
+
+ @Test
+ public void testNextLuxWhenAlreadyEnabledAndAvailable() {
+ testNextLuxWhenAlreadyEnabled(true /* dataIsAvailable */);
+ }
+
+ private void testNextLuxWhenAlreadyEnabled(boolean dataIsAvailable) {
+ final List<Integer> values = List.of(1, 2, 3, 4, 6);
+ mProbe.enable();
+
+ verify(mSensorManager).registerListener(
+ mSensorEventListenerCaptor.capture(), any(), anyInt());
+
+ if (dataIsAvailable) {
+ for (int v : values) {
+ mSensorEventListenerCaptor.getValue().onSensorChanged(
+ new SensorEvent(mLightSensor, 1, 1, new float[]{v}));
+ }
+ }
+ AtomicInteger lux = new AtomicInteger(-1);
+ mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
+ if (!dataIsAvailable) {
+ for (int v : values) {
+ mSensorEventListenerCaptor.getValue().onSensorChanged(
+ new SensorEvent(mLightSensor, 1, 1, new float[]{v}));
+ }
+ }
+
+ mSensorEventListenerCaptor.getValue().onSensorChanged(
+ new SensorEvent(mLightSensor, 1, 1, new float[]{200f}));
+
+ // should remain enabled
+ assertThat(lux.get()).isEqualTo(values.get(dataIsAvailable ? values.size() - 1 : 0));
+ verify(mSensorManager, never()).unregisterListener(any(SensorEventListener.class));
+ verifyNoMoreInteractions(mSensorManager);
+
+ final int anotherValue = 12;
+ mSensorEventListenerCaptor.getValue().onSensorChanged(
+ new SensorEvent(mLightSensor, 1, 1, new float[]{12}));
+ assertThat(mProbe.getMostRecentLux()).isEqualTo(anotherValue);
+ }
+
+ @Test
+ public void testNextLuxWhenNotEnabled() {
+ testNextLuxWhenNotEnabled(false /* enableWhileWaiting */);
+ }
+
+ @Test
+ public void testNextLuxWhenNotEnabledButEnabledLater() {
+ testNextLuxWhenNotEnabled(true /* enableWhileWaiting */);
+ }
+
+ private void testNextLuxWhenNotEnabled(boolean enableWhileWaiting) {
+ final List<Integer> values = List.of(1, 2, 3, 4, 6);
+ mProbe.disable();
+
+ AtomicInteger lux = new AtomicInteger(-1);
+ mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
+
+ if (enableWhileWaiting) {
+ mProbe.enable();
+ }
+
+ verify(mSensorManager).registerListener(
+ mSensorEventListenerCaptor.capture(), any(), anyInt());
+ for (int v : values) {
+ mSensorEventListenerCaptor.getValue().onSensorChanged(
+ new SensorEvent(mLightSensor, 1, 1, new float[]{v}));
+ }
+
+ // should restore the disabled state
+ assertThat(lux.get()).isEqualTo(values.get(0));
+ verify(mSensorManager, enableWhileWaiting ? never() : times(1)).unregisterListener(
+ any(SensorEventListener.class));
+ verifyNoMoreInteractions(mSensorManager);
+ }
+
+ @Test
+ public void testNextLuxIsNotCanceledByDisableOrDestroy() {
+ final int value = 7;
+ AtomicInteger lux = new AtomicInteger(-1);
+ mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
+
+ verify(mSensorManager).registerListener(
+ mSensorEventListenerCaptor.capture(), any(), anyInt());
+
+ mProbe.destroy();
+ mProbe.disable();
+
+ assertThat(lux.get()).isEqualTo(-1);
+
+ mSensorEventListenerCaptor.getValue().onSensorChanged(
+ new SensorEvent(mLightSensor, 1, 1, new float[]{value}));
+
+ assertThat(lux.get()).isEqualTo(value);
+
+ mSensorEventListenerCaptor.getValue().onSensorChanged(
+ new SensorEvent(mLightSensor, 1, 1, new float[]{value + 1}));
+
+ // should remain destroyed
+ mProbe.enable();
+
+ assertThat(lux.get()).isEqualTo(value);
+ verify(mSensorManager).unregisterListener(any(SensorEventListener.class));
+ verifyNoMoreInteractions(mSensorManager);
+ }
+
+ @Test
+ public void testMultipleNextConsumers() {
+ final int value = 7;
+ AtomicInteger lux = new AtomicInteger(-1);
+ AtomicInteger lux2 = new AtomicInteger(-1);
+ mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
+ mProbe.awaitNextLux((v) -> lux2.set(Math.round(v)), null /* handler */);
+
+ verify(mSensorManager).registerListener(
+ mSensorEventListenerCaptor.capture(), any(), anyInt());
+ mSensorEventListenerCaptor.getValue().onSensorChanged(
+ new SensorEvent(mLightSensor, 1, 1, new float[]{value}));
+
+ assertThat(lux.get()).isEqualTo(value);
+ assertThat(lux2.get()).isEqualTo(value);
+ }
+
+ @Test
+ public void testNoNextLuxWhenDestroyed() {
+ mProbe.destroy();
+
+ AtomicInteger lux = new AtomicInteger(-20);
+ mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
+
+ assertThat(lux.get()).isEqualTo(-1);
+ verify(mSensorManager, never()).registerListener(
+ mSensorEventListenerCaptor.capture(), any(), anyInt());
+ verifyNoMoreInteractions(mSensorManager);
}
private void moveTimeBy(long millis) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
index 60dc2eb..88a9646 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
@@ -121,7 +121,7 @@
verify(mSink).authenticate(eq(mOpContext),
eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(),
anyLong(), anyInt(), eq(requireConfirmation),
- eq(targetUserId), anyFloat());
+ eq(targetUserId), any());
}
@Test
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 dea4d4f..a5c181d 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
@@ -28,6 +28,7 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -215,7 +216,7 @@
@Test
public void luxProbeWhenAwake() throws RemoteException {
- when(mBiometricContext.isAwake()).thenReturn(false, true, false);
+ when(mBiometricContext.isAwake()).thenReturn(false);
when(mBiometricContext.isAod()).thenReturn(false);
final FingerprintAuthenticationClient client = createClient();
client.start(mCallback);
@@ -228,15 +229,38 @@
verify(mLuxProbe, never()).enable();
reset(mLuxProbe);
+ when(mBiometricContext.isAwake()).thenReturn(true);
+
mContextInjector.getValue().accept(opContext);
verify(mLuxProbe).enable();
verify(mLuxProbe, never()).disable();
+ when(mBiometricContext.isAwake()).thenReturn(false);
+
mContextInjector.getValue().accept(opContext);
verify(mLuxProbe).disable();
}
@Test
+ public void luxProbeEnabledOnStartWhenWake() throws RemoteException {
+ luxProbeEnabledOnStart(true /* isAwake */);
+ }
+
+ @Test
+ public void luxProbeNotEnabledOnStartWhenNotWake() throws RemoteException {
+ luxProbeEnabledOnStart(false /* isAwake */);
+ }
+
+ private void luxProbeEnabledOnStart(boolean isAwake) throws RemoteException {
+ when(mBiometricContext.isAwake()).thenReturn(isAwake);
+ when(mBiometricContext.isAod()).thenReturn(false);
+ final FingerprintAuthenticationClient client = createClient();
+ client.start(mCallback);
+
+ verify(mLuxProbe, isAwake ? times(1) : never()).enable();
+ }
+
+ @Test
public void luxProbeDisabledOnAod() throws RemoteException {
when(mBiometricContext.isAwake()).thenReturn(false);
when(mBiometricContext.isAod()).thenReturn(true);