summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apct-tests/perftests/rubidium/src/android/rubidium/js/JSScriptEnginePerfTests.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt493
-rw-r--r--packages/SystemUI/tests/res/drawable-nodpi/romainguy_rockaway.jpgbin0 -> 414841 bytes
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt346
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java24
-rw-r--r--services/core/java/com/android/server/am/ProcessErrorStateRecord.java27
-rw-r--r--services/core/java/com/android/server/am/StackTracesDumpHelper.java13
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java3
-rw-r--r--services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java2
-rw-r--r--services/core/java/com/android/server/wm/InsetsPolicy.java9
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java2
-rw-r--r--services/core/java/com/android/server/wm/InsetsStateController.java12
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowContainerInsetsSourceProvider.java34
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java (renamed from services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java)10
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java2
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java8
20 files changed, 959 insertions, 89 deletions
diff --git a/apct-tests/perftests/rubidium/src/android/rubidium/js/JSScriptEnginePerfTests.java b/apct-tests/perftests/rubidium/src/android/rubidium/js/JSScriptEnginePerfTests.java
index 0b351013d23a..cbd602f0de76 100644
--- a/apct-tests/perftests/rubidium/src/android/rubidium/js/JSScriptEnginePerfTests.java
+++ b/apct-tests/perftests/rubidium/src/android/rubidium/js/JSScriptEnginePerfTests.java
@@ -45,11 +45,13 @@ import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.adservices.data.adselection.CustomAudienceSignals;
-import com.android.adservices.service.adselection.AdDataArgument;
-import com.android.adservices.service.adselection.AdSelectionConfigArgument;
-import com.android.adservices.service.adselection.AdWithBidArgument;
-import com.android.adservices.service.adselection.CustomAudienceBiddingSignalsArgument;
-import com.android.adservices.service.adselection.CustomAudienceScoringSignalsArgument;
+import com.android.adservices.service.adselection.AdCounterKeyCopier;
+import com.android.adservices.service.adselection.AdCounterKeyCopierNoOpImpl;
+import com.android.adservices.service.adselection.AdDataArgumentUtil;
+import com.android.adservices.service.adselection.AdSelectionConfigArgumentUtil;
+import com.android.adservices.service.adselection.AdWithBidArgumentUtil;
+import com.android.adservices.service.adselection.CustomAudienceBiddingSignalsArgumentUtil;
+import com.android.adservices.service.adselection.CustomAudienceScoringSignalsArgumentUtil;
import com.android.adservices.service.js.IsolateSettings;
import com.android.adservices.service.js.JSScriptArgument;
import com.android.adservices.service.js.JSScriptArrayArgument;
@@ -106,6 +108,14 @@ public class JSScriptEnginePerfTests {
private static final Instant ACTIVATION_TIME = CLOCK.instant();
private static final Instant EXPIRATION_TIME = CLOCK.instant().plus(Duration.ofDays(1));
private static final AdSelectionSignals CONTEXTUAL_SIGNALS = AdSelectionSignals.EMPTY;
+ private static final AdCounterKeyCopier AD_COUNTER_KEY_COPIER_NO_OP =
+ new AdCounterKeyCopierNoOpImpl();
+
+ private final AdDataArgumentUtil mAdDataArgumentUtil =
+ new AdDataArgumentUtil(AD_COUNTER_KEY_COPIER_NO_OP);
+ private final AdWithBidArgumentUtil mAdWithBidArgumentUtil =
+ new AdWithBidArgumentUtil(mAdDataArgumentUtil);
+
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -437,7 +447,7 @@ public class JSScriptEnginePerfTests {
List<AdData> adDataList = getSampleAdDataList(numOfAds, "https://ads.example/");
ImmutableList.Builder<JSScriptArgument> adDataListArgument = new ImmutableList.Builder<>();
for (AdData adData : adDataList) {
- adDataListArgument.add(AdDataArgument.asScriptArgument("ignored", adData));
+ adDataListArgument.add(mAdDataArgumentUtil.asScriptArgument("ignored", adData));
}
AdSelectionSignals perBuyerSignals = generatePerBuyerSignals(numOfAds);
AdSelectionSignals auctionSignals = AdSelectionSignals.fromString("{\"auctionSignal1"
@@ -455,7 +465,7 @@ public class JSScriptEnginePerfTests {
.add(jsonArg("perBuyerSignals", perBuyerSignals))
.add(jsonArg("trustedBiddingSignals", trustedBiddingSignals))
.add(jsonArg("contextualSignals", CONTEXTUAL_SIGNALS))
- .add(CustomAudienceBiddingSignalsArgument.asScriptArgument(
+ .add(CustomAudienceBiddingSignalsArgumentUtil.asScriptArgument(
"customAudienceBiddingSignal", customAudienceSignals))
.build();
InputStream testJsInputStream = sContext.getAssets().open(
@@ -485,7 +495,8 @@ public class JSScriptEnginePerfTests {
ImmutableList.Builder<JSScriptArgument> adWithBidArrayArgument =
new ImmutableList.Builder<>();
for (AdWithBid adWithBid : adWithBidList) {
- adWithBidArrayArgument.add(AdWithBidArgument.asScriptArgument("adWithBid", adWithBid));
+ adWithBidArrayArgument.add(
+ mAdWithBidArgumentUtil.asScriptArgument("adWithBid", adWithBid));
}
AdTechIdentifier seller = AdTechIdentifier.fromString("www.example-ssp.com");
AdSelectionSignals sellerSignals = AdSelectionSignals.fromString("{\"signals\":[]}");
@@ -507,12 +518,12 @@ public class JSScriptEnginePerfTests {
ImmutableList<JSScriptArgument> args = ImmutableList.<JSScriptArgument>builder()
.add(arrayArg("adsWithBids", adWithBidArrayArgument.build()))
- .add(AdSelectionConfigArgument.asScriptArgument(adSelectionConfig,
+ .add(AdSelectionConfigArgumentUtil.asScriptArgument(adSelectionConfig,
"adSelectionConfig"))
.add(jsonArg("sellerSignals", sellerSignals))
.add(jsonArg("trustedScoringSignals", trustedScoringSignalsJson))
.add(jsonArg("contextualSignals", CONTEXTUAL_SIGNALS))
- .add(CustomAudienceScoringSignalsArgument.asScriptArgument(
+ .add(CustomAudienceScoringSignalsArgumentUtil.asScriptArgument(
"customAudienceScoringSignal", customAudienceSignals))
.build();
InputStream testJsInputStream = sContext.getAssets().open(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
index 64211b5b138e..d15a2afa0d4a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
@@ -39,7 +39,7 @@ constructor(
private fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) =
mainExecutor.execute {
action?.let {
- if (event.tracking) {
+ if (event.tracking || event.expanded) {
Log.v(TAG, "Detected panel interaction, event: $event")
it.onPanelInteraction.run()
disable()
diff --git a/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
new file mode 100644
index 000000000000..801b1652e487
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
@@ -0,0 +1,493 @@
+package com.android.systemui.graphics
+
+import android.annotation.AnyThread
+import android.annotation.DrawableRes
+import android.annotation.Px
+import android.annotation.SuppressLint
+import android.annotation.WorkerThread
+import android.content.Context
+import android.content.pm.PackageManager
+import android.content.res.Resources
+import android.content.res.Resources.NotFoundException
+import android.graphics.Bitmap
+import android.graphics.ImageDecoder
+import android.graphics.ImageDecoder.DecodeException
+import android.graphics.drawable.AdaptiveIconDrawable
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.util.Log
+import android.util.Size
+import androidx.core.content.res.ResourcesCompat
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import java.io.IOException
+import javax.inject.Inject
+import kotlin.math.min
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+/**
+ * Helper class to load images for SystemUI. It allows for memory efficient image loading with size
+ * restriction and attempts to use hardware bitmaps when sensible.
+ */
+@SysUISingleton
+class ImageLoader
+@Inject
+constructor(
+ private val defaultContext: Context,
+ @Background private val backgroundDispatcher: CoroutineDispatcher
+) {
+
+ /** Source of the image data. */
+ sealed interface Source
+
+ /**
+ * Load image from a Resource ID. If the resource is part of another package or if it requires
+ * tinting, pass in a correct [Context].
+ */
+ data class Res(@DrawableRes val resId: Int, val context: Context?) : Source {
+ constructor(@DrawableRes resId: Int) : this(resId, null)
+ }
+
+ /** Load image from a Uri. */
+ data class Uri(val uri: android.net.Uri) : Source {
+ constructor(uri: String) : this(android.net.Uri.parse(uri))
+ }
+
+ /** Load image from a [File]. */
+ data class File(val file: java.io.File) : Source {
+ constructor(path: String) : this(java.io.File(path))
+ }
+
+ /** Load image from an [InputStream]. */
+ data class InputStream(val inputStream: java.io.InputStream, val context: Context?) : Source {
+ constructor(inputStream: java.io.InputStream) : this(inputStream, null)
+ }
+
+ /**
+ * Loads passed [Source] on a background thread and returns the [Bitmap].
+ *
+ * Maximum height and width can be passed as optional parameters - the image decoder will make
+ * sure to keep the decoded drawable size within those passed constraints while keeping aspect
+ * ratio.
+ *
+ * @param maxWidth Maximum width of the returned drawable (if able). 0 means no restriction. Set
+ * to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param maxHeight Maximum height of the returned drawable (if able). 0 means no restriction.
+ * Set to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param allocator Allocator to use for the loaded drawable - one of [ImageDecoder] allocator
+ * ints. Use [ImageDecoder.ALLOCATOR_SOFTWARE] to force software bitmap.
+ * @return loaded [Bitmap] or `null` if loading failed.
+ */
+ @AnyThread
+ suspend fun loadBitmap(
+ source: Source,
+ @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+ ): Bitmap? =
+ withContext(backgroundDispatcher) { loadBitmapSync(source, maxWidth, maxHeight, allocator) }
+
+ /**
+ * Loads passed [Source] synchronously and returns the [Bitmap].
+ *
+ * Maximum height and width can be passed as optional parameters - the image decoder will make
+ * sure to keep the decoded drawable size within those passed constraints while keeping aspect
+ * ratio.
+ *
+ * @param maxWidth Maximum width of the returned drawable (if able). 0 means no restriction. Set
+ * to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param maxHeight Maximum height of the returned drawable (if able). 0 means no restriction.
+ * Set to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param allocator Allocator to use for the loaded drawable - one of [ImageDecoder] allocator
+ * ints. Use [ImageDecoder.ALLOCATOR_SOFTWARE] to force software bitmap.
+ * @return loaded [Bitmap] or `null` if loading failed.
+ */
+ @WorkerThread
+ fun loadBitmapSync(
+ source: Source,
+ @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+ ): Bitmap? {
+ return try {
+ loadBitmapSync(
+ toImageDecoderSource(source, defaultContext),
+ maxWidth,
+ maxHeight,
+ allocator
+ )
+ } catch (e: NotFoundException) {
+ Log.w(TAG, "Couldn't load resource $source", e)
+ null
+ }
+ }
+
+ /**
+ * Loads passed [ImageDecoder.Source] synchronously and returns the drawable.
+ *
+ * Maximum height and width can be passed as optional parameters - the image decoder will make
+ * sure to keep the decoded drawable size within those passed constraints (while keeping aspect
+ * ratio).
+ *
+ * @param maxWidth Maximum width of the returned drawable (if able). 0 means no restriction. Set
+ * to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param maxHeight Maximum height of the returned drawable (if able). 0 means no restriction.
+ * Set to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param allocator Allocator to use for the loaded drawable - one of [ImageDecoder] allocator
+ * ints. Use [ImageDecoder.ALLOCATOR_SOFTWARE] to force software bitmap.
+ * @return loaded [Bitmap] or `null` if loading failed.
+ */
+ @WorkerThread
+ fun loadBitmapSync(
+ source: ImageDecoder.Source,
+ @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+ ): Bitmap? {
+ return try {
+ ImageDecoder.decodeBitmap(source) { decoder, info, _ ->
+ configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
+ decoder.allocator = allocator
+ }
+ } catch (e: IOException) {
+ Log.w(TAG, "Failed to load source $source", e)
+ return null
+ } catch (e: DecodeException) {
+ Log.w(TAG, "Failed to decode source $source", e)
+ return null
+ }
+ }
+
+ /**
+ * Loads passed [Source] on a background thread and returns the [Drawable].
+ *
+ * Maximum height and width can be passed as optional parameters - the image decoder will make
+ * sure to keep the decoded drawable size within those passed constraints (while keeping aspect
+ * ratio).
+ *
+ * @param maxWidth Maximum width of the returned drawable (if able). 0 means no restriction. Set
+ * to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param maxHeight Maximum height of the returned drawable (if able). 0 means no restriction.
+ * Set to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param allocator Allocator to use for the loaded drawable - one of [ImageDecoder] allocator
+ * ints. Use [ImageDecoder.ALLOCATOR_SOFTWARE] to force software bitmap.
+ * @return loaded [Drawable] or `null` if loading failed.
+ */
+ @AnyThread
+ suspend fun loadDrawable(
+ source: Source,
+ @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+ ): Drawable? =
+ withContext(backgroundDispatcher) {
+ loadDrawableSync(source, maxWidth, maxHeight, allocator)
+ }
+
+ /**
+ * Loads passed [Icon] on a background thread and returns the drawable.
+ *
+ * Maximum height and width can be passed as optional parameters - the image decoder will make
+ * sure to keep the decoded drawable size within those passed constraints (while keeping aspect
+ * ratio).
+ *
+ * @param context Alternate context to use for resource loading (for e.g. cross-process use)
+ * @param maxWidth Maximum width of the returned drawable (if able). 0 means no restriction. Set
+ * to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param maxHeight Maximum height of the returned drawable (if able). 0 means no restriction.
+ * Set to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param allocator Allocator to use for the loaded drawable - one of [ImageDecoder] allocator
+ * ints. Use [ImageDecoder.ALLOCATOR_SOFTWARE] to force software bitmap.
+ * @return loaded [Drawable] or `null` if loading failed.
+ */
+ @AnyThread
+ suspend fun loadDrawable(
+ icon: Icon,
+ context: Context = defaultContext,
+ @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+ ): Drawable? =
+ withContext(backgroundDispatcher) {
+ loadDrawableSync(icon, context, maxWidth, maxHeight, allocator)
+ }
+
+ /**
+ * Loads passed [Source] synchronously and returns the drawable.
+ *
+ * Maximum height and width can be passed as optional parameters - the image decoder will make
+ * sure to keep the decoded drawable size within those passed constraints (while keeping aspect
+ * ratio).
+ *
+ * @param maxWidth Maximum width of the returned drawable (if able). 0 means no restriction. Set
+ * to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param maxHeight Maximum height of the returned drawable (if able). 0 means no restriction.
+ * Set to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param allocator Allocator to use for the loaded drawable - one of [ImageDecoder] allocator
+ * ints. Use [ImageDecoder.ALLOCATOR_SOFTWARE] to force software bitmap.
+ * @return loaded [Drawable] or `null` if loading failed.
+ */
+ @WorkerThread
+ @SuppressLint("UseCompatLoadingForDrawables")
+ fun loadDrawableSync(
+ source: Source,
+ @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+ ): Drawable? {
+ return try {
+ loadDrawableSync(
+ toImageDecoderSource(source, defaultContext),
+ maxWidth,
+ maxHeight,
+ allocator
+ )
+ ?:
+ // If we have a resource, retry fallback using the "normal" Resource loading system.
+ // This will come into effect in cases like trying to load AnimatedVectorDrawable.
+ if (source is Res) {
+ val context = source.context ?: defaultContext
+ ResourcesCompat.getDrawable(context.resources, source.resId, context.theme)
+ } else {
+ null
+ }
+ } catch (e: NotFoundException) {
+ Log.w(TAG, "Couldn't load resource $source", e)
+ null
+ }
+ }
+
+ /**
+ * Loads passed [ImageDecoder.Source] synchronously and returns the drawable.
+ *
+ * Maximum height and width can be passed as optional parameters - the image decoder will make
+ * sure to keep the decoded drawable size within those passed constraints (while keeping aspect
+ * ratio).
+ *
+ * @param maxWidth Maximum width of the returned drawable (if able). 0 means no restriction. Set
+ * to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param maxHeight Maximum height of the returned drawable (if able). 0 means no restriction.
+ * Set to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param allocator Allocator to use for the loaded drawable - one of [ImageDecoder] allocator
+ * ints. Use [ImageDecoder.ALLOCATOR_SOFTWARE] to force software bitmap.
+ * @return loaded [Drawable] or `null` if loading failed.
+ */
+ @WorkerThread
+ fun loadDrawableSync(
+ source: ImageDecoder.Source,
+ @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+ ): Drawable? {
+ return try {
+ ImageDecoder.decodeDrawable(source) { decoder, info, _ ->
+ configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
+ decoder.allocator = allocator
+ }
+ } catch (e: IOException) {
+ Log.w(TAG, "Failed to load source $source", e)
+ return null
+ } catch (e: DecodeException) {
+ Log.w(TAG, "Failed to decode source $source", e)
+ return null
+ }
+ }
+
+ /** Loads icon drawable while attempting to size restrict the drawable. */
+ @WorkerThread
+ fun loadDrawableSync(
+ icon: Icon,
+ context: Context = defaultContext,
+ @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+ ): Drawable? {
+ return when (icon.type) {
+ Icon.TYPE_URI,
+ Icon.TYPE_URI_ADAPTIVE_BITMAP -> {
+ val source = ImageDecoder.createSource(context.contentResolver, icon.uri)
+ loadDrawableSync(source, maxWidth, maxHeight, allocator)
+ }
+ Icon.TYPE_RESOURCE -> {
+ val resources = resolveResourcesForIcon(context, icon)
+ resources?.let {
+ loadDrawableSync(
+ ImageDecoder.createSource(it, icon.resId),
+ maxWidth,
+ maxHeight,
+ allocator
+ )
+ }
+ // Fallback to non-ImageDecoder load if the attempt failed (e.g. the resource
+ // is a Vector drawable which ImageDecoder doesn't support.)
+ ?: icon.loadDrawable(context)
+ }
+ Icon.TYPE_BITMAP -> {
+ BitmapDrawable(context.resources, icon.bitmap)
+ }
+ Icon.TYPE_ADAPTIVE_BITMAP -> {
+ AdaptiveIconDrawable(null, BitmapDrawable(context.resources, icon.bitmap))
+ }
+ Icon.TYPE_DATA -> {
+ loadDrawableSync(
+ ImageDecoder.createSource(icon.dataBytes, icon.dataOffset, icon.dataLength),
+ maxWidth,
+ maxHeight,
+ allocator
+ )
+ }
+ else -> {
+ // We don't recognize this icon, just fallback.
+ icon.loadDrawable(context)
+ }
+ }?.let { drawable ->
+ // Icons carry tint which we need to propagate down to a Drawable.
+ tintDrawable(icon, drawable)
+ drawable
+ }
+ }
+
+ companion object {
+ const val TAG = "ImageLoader"
+
+ // 4096 is a reasonable default - most devices will support 4096x4096 texture size for
+ // Canvas rendering and by default we SystemUI has no need to render larger bitmaps.
+ // This prevents exceptions and crashes if the code accidentally loads larger Bitmap
+ // and then attempts to render it on Canvas.
+ // It can always be overridden by the parameters.
+ const val DEFAULT_MAX_SAFE_BITMAP_SIZE_PX = 4096
+
+ /**
+ * This constant signals that ImageLoader shouldn't attempt to resize the passed bitmap in a
+ * given dimension.
+ *
+ * Set both maxWidth and maxHeight to [DO_NOT_RESIZE] if you wish to prevent resizing.
+ */
+ const val DO_NOT_RESIZE = 0
+
+ /** Maps [Source] to [ImageDecoder.Source]. */
+ private fun toImageDecoderSource(source: Source, defaultContext: Context) =
+ when (source) {
+ is Res -> {
+ val context = source.context ?: defaultContext
+ ImageDecoder.createSource(context.resources, source.resId)
+ }
+ is File -> ImageDecoder.createSource(source.file)
+ is Uri -> ImageDecoder.createSource(defaultContext.contentResolver, source.uri)
+ is InputStream -> {
+ val context = source.context ?: defaultContext
+ ImageDecoder.createSource(context.resources, source.inputStream)
+ }
+ }
+
+ /**
+ * This sets target size on the image decoder to conform to the maxWidth / maxHeight
+ * parameters. The parameters are chosen to keep the existing drawable aspect ratio.
+ */
+ @AnyThread
+ private fun configureDecoderForMaximumSize(
+ decoder: ImageDecoder,
+ imgSize: Size,
+ @Px maxWidth: Int,
+ @Px maxHeight: Int
+ ) {
+ if (maxWidth == DO_NOT_RESIZE && maxHeight == DO_NOT_RESIZE) {
+ return
+ }
+
+ if (imgSize.width <= maxWidth && imgSize.height <= maxHeight) {
+ return
+ }
+
+ // Determine the scale factor for each dimension so it fits within the set constraint
+ val wScale =
+ if (maxWidth <= 0) {
+ 1.0f
+ } else {
+ maxWidth.toFloat() / imgSize.width.toFloat()
+ }
+
+ val hScale =
+ if (maxHeight <= 0) {
+ 1.0f
+ } else {
+ maxHeight.toFloat() / imgSize.height.toFloat()
+ }
+
+ // Scale down to the dimension that demands larger scaling (smaller scale factor).
+ // Use the same scale for both dimensions to keep the aspect ratio.
+ val scale = min(wScale, hScale)
+ if (scale < 1.0f) {
+ val targetWidth = (imgSize.width * scale).toInt()
+ val targetHeight = (imgSize.height * scale).toInt()
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Configured image size to $targetWidth x $targetHeight")
+ }
+
+ decoder.setTargetSize(targetWidth, targetHeight)
+ }
+ }
+
+ /**
+ * Attempts to retrieve [Resources] class required to load the passed icon. Icons can
+ * originate from other processes so we need to make sure we load them from the right
+ * package source.
+ *
+ * @return [Resources] to load the icon drawble or null if icon doesn't carry a resource or
+ * the resource package couldn't be resolved.
+ */
+ @WorkerThread
+ private fun resolveResourcesForIcon(context: Context, icon: Icon): Resources? {
+ if (icon.type != Icon.TYPE_RESOURCE) {
+ return null
+ }
+
+ val resources = icon.resources
+ if (resources != null) {
+ return resources
+ }
+
+ val resPackage = icon.resPackage
+ if (
+ resPackage == null || resPackage.isEmpty() || context.packageName.equals(resPackage)
+ ) {
+ return context.resources
+ }
+
+ if ("android" == resPackage) {
+ return Resources.getSystem()
+ }
+
+ val pm = context.packageManager
+ try {
+ val ai =
+ pm.getApplicationInfo(
+ resPackage,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES or
+ PackageManager.GET_SHARED_LIBRARY_FILES
+ )
+ if (ai != null) {
+ return pm.getResourcesForApplication(ai)
+ } else {
+ Log.w(TAG, "Failed to resolve application info for $resPackage")
+ }
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(TAG, "Failed to resolve resource package", e)
+ return null
+ }
+ return null
+ }
+
+ /** Applies tinting from [Icon] to the passed [Drawable]. */
+ @AnyThread
+ private fun tintDrawable(icon: Icon, drawable: Drawable) {
+ if (icon.hasTint()) {
+ drawable.mutate()
+ drawable.setTintList(icon.tintList)
+ drawable.setTintBlendMode(icon.tintBlendMode)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/res/drawable-nodpi/romainguy_rockaway.jpg b/packages/SystemUI/tests/res/drawable-nodpi/romainguy_rockaway.jpg
new file mode 100644
index 000000000000..68473ba6c962
--- /dev/null
+++ b/packages/SystemUI/tests/res/drawable-nodpi/romainguy_rockaway.jpg
Binary files differ
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
index 6ddba0b4719c..b41053cdea50 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
@@ -25,7 +25,7 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.timeout
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.junit.MockitoJUnit
@@ -49,17 +49,31 @@ class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() {
}
@Test
- fun testEnableDetector_shouldPostRunnable() {
+ fun testEnableDetector_expandWithTrack_shouldPostRunnable() {
detector.enable(action)
// simulate notification expand
shadeExpansionStateManager.onPanelExpansionChanged(5566f, true, true, 5566f)
- verify(action, timeout(5000).times(1)).run()
+ verify(action).run()
+ }
+
+ @Test
+ fun testEnableDetector_trackOnly_shouldPostRunnable() {
+ detector.enable(action)
+ // simulate notification expand
+ shadeExpansionStateManager.onPanelExpansionChanged(5566f, false, true, 5566f)
+ verify(action).run()
+ }
+
+ @Test
+ fun testEnableDetector_expandOnly_shouldPostRunnable() {
+ detector.enable(action)
+ // simulate notification expand
+ shadeExpansionStateManager.onPanelExpansionChanged(5566f, true, false, 5566f)
+ verify(action).run()
}
@Test
fun testEnableDetector_shouldNotPostRunnable() {
- var detector =
- AuthDialogPanelInteractionDetector(shadeExpansionStateManager, mContext.mainExecutor)
detector.enable(action)
detector.disable()
shadeExpansionStateManager.onPanelExpansionChanged(5566f, true, true, 5566f)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt
new file mode 100644
index 000000000000..ccd631ec37d0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt
@@ -0,0 +1,346 @@
+package com.android.systemui.graphics
+
+import android.content.res.Resources
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.ImageDecoder
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.graphics.drawable.VectorDrawable
+import android.net.Uri
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+@RunWith(AndroidJUnit4::class)
+class ImageLoaderTest : SysuiTestCase() {
+
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+ private val imageLoader = ImageLoader(context, testDispatcher)
+
+ private lateinit var imgFile: File
+
+ @Before
+ fun setUp() {
+ val context = context.createPackageContext("com.android.systemui.tests", 0)
+ val bitmap =
+ BitmapFactory.decodeResource(
+ context.resources,
+ com.android.systemui.tests.R.drawable.romainguy_rockaway
+ )
+
+ imgFile = File.createTempFile("image", ".png", context.cacheDir)
+ imgFile.deleteOnExit()
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, FileOutputStream(imgFile))
+ }
+
+ @After
+ fun tearDown() {
+ imgFile.delete()
+ }
+
+ @Test
+ fun invalidResource_drawable_returnsNull() =
+ testScope.runTest { assertThat(imageLoader.loadDrawable(ImageLoader.Res(-1))).isNull() }
+
+ @Test
+ fun invalidResource_bitmap_returnsNull() =
+ testScope.runTest { assertThat(imageLoader.loadBitmap(ImageLoader.Res(-1))).isNull() }
+
+ @Test
+ fun invalidUri_returnsNull() =
+ testScope.runTest {
+ assertThat(imageLoader.loadBitmap(ImageLoader.Uri("this.is/bogus"))).isNull()
+ }
+
+ @Test
+ fun invalidFile_returnsNull() =
+ testScope.runTest {
+ assertThat(imageLoader.loadBitmap(ImageLoader.File("this is broken!"))).isNull()
+ }
+
+ @Test
+ fun invalidIcon_returnsNull() =
+ testScope.runTest {
+ assertThat(imageLoader.loadDrawable(Icon.createWithFilePath("this is broken"))).isNull()
+ }
+
+ @Test
+ fun invalidIS_returnsNull() =
+ testScope.runTest {
+ assertThat(
+ imageLoader.loadDrawable(
+ ImageLoader.InputStream(ByteArrayInputStream(ByteArray(0)))
+ )
+ )
+ .isNull()
+ }
+
+ @Test
+ fun validBitmapResource_loadDrawable_returnsBitmapDrawable() =
+ testScope.runTest {
+ val context = context.createPackageContext("com.android.systemui.tests", 0)
+ val bitmap =
+ BitmapFactory.decodeResource(
+ context.resources,
+ com.android.systemui.tests.R.drawable.romainguy_rockaway
+ )
+ assertThat(bitmap).isNotNull()
+ val loadedDrawable =
+ imageLoader.loadDrawable(
+ ImageLoader.Res(
+ com.android.systemui.tests.R.drawable.romainguy_rockaway,
+ context
+ )
+ )
+ assertBitmapEqualToDrawable(loadedDrawable, bitmap)
+ }
+
+ @Test
+ fun validBitmapResource_loadBitmap_returnsBitmapDrawable() =
+ testScope.runTest {
+ val bitmap =
+ BitmapFactory.decodeResource(
+ context.resources,
+ R.drawable.dessert_zombiegingerbread
+ )
+ val loadedBitmap =
+ imageLoader.loadBitmap(ImageLoader.Res(R.drawable.dessert_zombiegingerbread))
+ assertBitmapEqualToBitmap(loadedBitmap, bitmap)
+ }
+
+ @Test
+ fun validBitmapUri_returnsBitmapDrawable() =
+ testScope.runTest {
+ val bitmap =
+ BitmapFactory.decodeResource(
+ context.resources,
+ R.drawable.dessert_zombiegingerbread
+ )
+
+ val uri =
+ "android.resource://${context.packageName}/${R.drawable.dessert_zombiegingerbread}"
+ val loadedBitmap = imageLoader.loadBitmap(ImageLoader.Uri(uri))
+ assertBitmapEqualToBitmap(loadedBitmap, bitmap)
+ }
+
+ @Test
+ fun validBitmapFile_returnsBitmapDrawable() =
+ testScope.runTest {
+ val bitmap = BitmapFactory.decodeFile(imgFile.absolutePath)
+ val loadedBitmap = imageLoader.loadBitmap(ImageLoader.File(imgFile))
+ assertBitmapEqualToBitmap(loadedBitmap, bitmap)
+ }
+
+ @Test
+ fun validInputStream_returnsBitmapDrawable() =
+ testScope.runTest {
+ val bitmap = BitmapFactory.decodeFile(imgFile.absolutePath)
+ val loadedBitmap =
+ imageLoader.loadBitmap(ImageLoader.InputStream(FileInputStream(imgFile)))
+ assertBitmapEqualToBitmap(loadedBitmap, bitmap)
+ }
+
+ @Test
+ fun validBitmapIcon_returnsBitmapDrawable() =
+ testScope.runTest {
+ val bitmap =
+ BitmapFactory.decodeResource(
+ context.resources,
+ R.drawable.dessert_zombiegingerbread
+ )
+ val loadedDrawable = imageLoader.loadDrawable(Icon.createWithBitmap(bitmap))
+ assertBitmapEqualToDrawable(loadedDrawable, bitmap)
+ }
+
+ @Test
+ fun validUriIcon_returnsBitmapDrawable() =
+ testScope.runTest {
+ val bitmap =
+ BitmapFactory.decodeResource(
+ context.resources,
+ R.drawable.dessert_zombiegingerbread
+ )
+ val uri =
+ "android.resource://${context.packageName}/${R.drawable.dessert_zombiegingerbread}"
+ val loadedDrawable = imageLoader.loadDrawable(Icon.createWithContentUri(Uri.parse(uri)))
+ assertBitmapEqualToDrawable(loadedDrawable, bitmap)
+ }
+
+ @Test
+ fun validDataIcon_returnsBitmapDrawable() =
+ testScope.runTest {
+ val bitmap =
+ BitmapFactory.decodeResource(
+ context.resources,
+ R.drawable.dessert_zombiegingerbread
+ )
+ val bos =
+ ByteArrayOutputStream(
+ bitmap.byteCount * 2
+ ) // Compressed bitmap should be smaller than its source.
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos)
+
+ val array = bos.toByteArray()
+ val loadedDrawable = imageLoader.loadDrawable(Icon.createWithData(array, 0, array.size))
+ assertBitmapEqualToDrawable(loadedDrawable, bitmap)
+ }
+
+ @Test
+ fun validSystemResourceIcon_returnsBitmapDrawable() =
+ testScope.runTest {
+ val bitmap =
+ Resources.getSystem().getDrawable(android.R.drawable.ic_dialog_alert, context.theme)
+ val loadedDrawable =
+ imageLoader.loadDrawable(
+ Icon.createWithResource("android", android.R.drawable.ic_dialog_alert)
+ )
+ assertBitmapEqualToDrawable(loadedDrawable, (bitmap as BitmapDrawable).bitmap)
+ }
+
+ @Test
+ fun invalidDifferentPackageResourceIcon_returnsNull() =
+ testScope.runTest {
+ val loadedDrawable =
+ imageLoader.loadDrawable(
+ Icon.createWithResource(
+ "noooope.wrong.package",
+ R.drawable.dessert_zombiegingerbread
+ )
+ )
+ assertThat(loadedDrawable).isNull()
+ }
+
+ @Test
+ fun validBitmapResource_widthMoreRestricted_downsizesKeepingAspectRatio() =
+ testScope.runTest {
+ val loadedDrawable =
+ imageLoader.loadDrawable(ImageLoader.File(imgFile), maxWidth = 160, maxHeight = 160)
+ val loadedBitmap = assertBitmapInDrawable(loadedDrawable)
+ assertThat(loadedBitmap.width).isEqualTo(160)
+ assertThat(loadedBitmap.height).isEqualTo(106)
+ }
+
+ @Test
+ fun validBitmapResource_heightMoreRestricted_downsizesKeepingAspectRatio() =
+ testScope.runTest {
+ val loadedDrawable =
+ imageLoader.loadDrawable(ImageLoader.File(imgFile), maxWidth = 160, maxHeight = 50)
+ val loadedBitmap = assertBitmapInDrawable(loadedDrawable)
+ assertThat(loadedBitmap.width).isEqualTo(74)
+ assertThat(loadedBitmap.height).isEqualTo(50)
+ }
+
+ @Test
+ fun validBitmapResource_onlyWidthRestricted_downsizesKeepingAspectRatio() =
+ testScope.runTest {
+ val loadedDrawable =
+ imageLoader.loadDrawable(
+ ImageLoader.File(imgFile),
+ maxWidth = 160,
+ maxHeight = ImageLoader.DO_NOT_RESIZE
+ )
+ val loadedBitmap = assertBitmapInDrawable(loadedDrawable)
+ assertThat(loadedBitmap.width).isEqualTo(160)
+ assertThat(loadedBitmap.height).isEqualTo(106)
+ }
+
+ @Test
+ fun validBitmapResource_onlyHeightRestricted_downsizesKeepingAspectRatio() =
+ testScope.runTest {
+ val loadedDrawable =
+ imageLoader.loadDrawable(
+ ImageLoader.Res(R.drawable.bubble_thumbnail),
+ maxWidth = ImageLoader.DO_NOT_RESIZE,
+ maxHeight = 120
+ )
+ val loadedBitmap = assertBitmapInDrawable(loadedDrawable)
+ assertThat(loadedBitmap.width).isEqualTo(123)
+ assertThat(loadedBitmap.height).isEqualTo(120)
+ }
+
+ @Test
+ fun validVectorDrawable_loadDrawable_successfullyLoaded() =
+ testScope.runTest {
+ val loadedDrawable = imageLoader.loadDrawable(ImageLoader.Res(R.drawable.ic_settings))
+ assertThat(loadedDrawable).isNotNull()
+ assertThat(loadedDrawable).isInstanceOf(VectorDrawable::class.java)
+ }
+
+ @Test
+ fun validVectorDrawable_loadBitmap_returnsNull() =
+ testScope.runTest {
+ val loadedBitmap = imageLoader.loadBitmap(ImageLoader.Res(R.drawable.ic_settings))
+ assertThat(loadedBitmap).isNull()
+ }
+
+ @Test
+ fun validVectorDrawableIcon_loadDrawable_successfullyLoaded() =
+ testScope.runTest {
+ val loadedDrawable =
+ imageLoader.loadDrawable(Icon.createWithResource(context, R.drawable.ic_settings))
+ assertThat(loadedDrawable).isNotNull()
+ assertThat(loadedDrawable).isInstanceOf(VectorDrawable::class.java)
+ }
+
+ @Test
+ fun hardwareAllocator_returnsHardwareBitmap() =
+ testScope.runTest {
+ val loadedDrawable =
+ imageLoader.loadDrawable(
+ ImageLoader.File(imgFile),
+ allocator = ImageDecoder.ALLOCATOR_HARDWARE
+ )
+ assertThat(loadedDrawable).isNotNull()
+ assertThat((loadedDrawable as BitmapDrawable).bitmap.config)
+ .isEqualTo(Bitmap.Config.HARDWARE)
+ }
+
+ @Test
+ fun softwareAllocator_returnsSoftwareBitmap() =
+ testScope.runTest {
+ val loadedDrawable =
+ imageLoader.loadDrawable(
+ ImageLoader.File(imgFile),
+ allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+ )
+ assertThat(loadedDrawable).isNotNull()
+ assertThat((loadedDrawable as BitmapDrawable).bitmap.config)
+ .isNotEqualTo(Bitmap.Config.HARDWARE)
+ }
+
+ private fun assertBitmapInDrawable(drawable: Drawable?): Bitmap {
+ assertThat(drawable).isNotNull()
+ assertThat(drawable).isInstanceOf(BitmapDrawable::class.java)
+ return (drawable as BitmapDrawable).bitmap
+ }
+
+ private fun assertBitmapEqualToDrawable(actual: Drawable?, expected: Bitmap) {
+ val actualBitmap = assertBitmapInDrawable(actual)
+ assertBitmapEqualToBitmap(actualBitmap, expected)
+ }
+
+ private fun assertBitmapEqualToBitmap(actual: Bitmap?, expected: Bitmap) {
+ assertThat(actual).isNotNull()
+ assertThat(actual?.width).isEqualTo(expected.width)
+ assertThat(actual?.height).isEqualTo(expected.height)
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index b338d89a0169..1363ef31c68d 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -1046,18 +1046,30 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
*/
void showToastWhereUidIsRunning(int uid, String text, @Toast.Duration int duration,
Looper looper) {
+ ArrayList<Integer> displayIdsForUid = getDisplayIdsWhereUidIsRunning(uid);
+ if (displayIdsForUid.isEmpty()) {
+ return;
+ }
+ DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+ for (int i = 0; i < displayIdsForUid.size(); i++) {
+ Display display = displayManager.getDisplay(displayIdsForUid.get(i));
+ if (display != null && display.isValid()) {
+ Toast.makeText(mContext.createDisplayContext(display), looper, text,
+ duration).show();
+ }
+ }
+ }
+
+ private ArrayList<Integer> getDisplayIdsWhereUidIsRunning(int uid) {
+ ArrayList<Integer> displayIdsForUid = new ArrayList<>();
synchronized (mVirtualDeviceLock) {
- DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
for (int i = 0; i < mVirtualDisplays.size(); i++) {
if (mVirtualDisplays.valueAt(i).getWindowPolicyController().containsUid(uid)) {
- Display display = displayManager.getDisplay(mVirtualDisplays.keyAt(i));
- if (display != null && display.isValid()) {
- Toast.makeText(mContext.createDisplayContext(display), looper, text,
- duration).show();
- }
+ displayIdsForUid.add(mVirtualDisplays.keyAt(i));
}
}
}
+ return displayIdsForUid;
}
boolean isDisplayOwnedByVirtualDevice(int displayId) {
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 1d48cb25f03a..e66894b596e9 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -22,7 +22,9 @@ import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ANR;
import static com.android.server.am.ActivityManagerService.MY_PID;
import static com.android.server.am.ProcessRecord.TAG;
+import static com.android.server.stats.pull.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AnrController;
import android.app.ApplicationErrorReport;
@@ -56,6 +58,7 @@ import com.android.internal.os.anr.AnrLatencyTracker;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.ResourcePressureUtil;
import com.android.server.criticalevents.CriticalEventLog;
+import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot;
import com.android.server.wm.WindowProcessController;
import java.io.File;
@@ -396,6 +399,8 @@ class ProcessErrorStateRecord {
});
}
}
+ // Build memory headers for the ANRing process.
+ String memoryHeaders = buildMemoryHeadersFor(pid);
// Get critical event log before logging the ANR so that it doesn't occur in the log.
latencyTracker.criticalEventLogStarted();
@@ -496,7 +501,7 @@ class ProcessErrorStateRecord {
File tracesFile = StackTracesDumpHelper.dumpStackTraces(firstPids,
isSilentAnr ? null : processCpuTracker, isSilentAnr ? null : lastPids,
nativePidsFuture, tracesFileException, firstPidEndOffset, annotation,
- criticalEventLog, auxiliaryTaskExecutor, latencyTracker);
+ criticalEventLog, memoryHeaders, auxiliaryTaskExecutor, latencyTracker);
if (isMonitorCpuUsage()) {
// Wait for the first call to finish
@@ -710,6 +715,26 @@ class ProcessErrorStateRecord {
resolver.getUserId()) != 0;
}
+ private @Nullable String buildMemoryHeadersFor(int pid) {
+ if (pid <= 0) {
+ Slog.i(TAG, "Memory header requested with invalid pid: " + pid);
+ return null;
+ }
+ MemorySnapshot snapshot = readMemorySnapshotFromProcfs(pid);
+ if (snapshot == null) {
+ Slog.i(TAG, "Failed to get memory snapshot for pid:" + pid);
+ return null;
+ }
+
+ StringBuilder memoryHeaders = new StringBuilder();
+ memoryHeaders.append("RssHwmKb: ")
+ .append(snapshot.rssHighWaterMarkInKilobytes)
+ .append("\n");
+ memoryHeaders.append("RssKb: ").append(snapshot.rssInKilobytes).append("\n");
+ memoryHeaders.append("RssAnonKb: ").append(snapshot.anonRssInKilobytes).append("\n");
+ memoryHeaders.append("VmSwapKb: ").append(snapshot.swapInKilobytes).append("\n");
+ return memoryHeaders.toString();
+ }
/**
* Unless configured otherwise, swallow ANRs in background processes & kill the process.
* Non-private access is for tests only.
diff --git a/services/core/java/com/android/server/am/StackTracesDumpHelper.java b/services/core/java/com/android/server/am/StackTracesDumpHelper.java
index 937332894dbd..10ddc2f562dc 100644
--- a/services/core/java/com/android/server/am/StackTracesDumpHelper.java
+++ b/services/core/java/com/android/server/am/StackTracesDumpHelper.java
@@ -85,7 +85,8 @@ public class StackTracesDumpHelper {
Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
@NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) {
return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture,
- logExceptionCreatingFile, null, null, null, auxiliaryTaskExecutor, latencyTracker);
+ logExceptionCreatingFile, null, null, null, null, auxiliaryTaskExecutor,
+ latencyTracker);
}
/**
@@ -99,7 +100,7 @@ public class StackTracesDumpHelper {
AnrLatencyTracker latencyTracker) {
return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture,
logExceptionCreatingFile, null, subject, criticalEventSection,
- auxiliaryTaskExecutor, latencyTracker);
+ /* memoryHeaders= */ null, auxiliaryTaskExecutor, latencyTracker);
}
/**
@@ -110,7 +111,8 @@ public class StackTracesDumpHelper {
ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
AtomicLong firstPidEndOffset, String subject, String criticalEventSection,
- @NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) {
+ String memoryHeaders, @NonNull Executor auxiliaryTaskExecutor,
+ AnrLatencyTracker latencyTracker) {
try {
if (latencyTracker != null) {
@@ -150,9 +152,10 @@ public class StackTracesDumpHelper {
return null;
}
- if (subject != null || criticalEventSection != null) {
+ if (subject != null || criticalEventSection != null || memoryHeaders != null) {
appendtoANRFile(tracesFile.getAbsolutePath(),
- (subject != null ? "Subject: " + subject + "\n\n" : "")
+ (subject != null ? "Subject: " + subject + "\n" : "")
+ + (memoryHeaders != null ? memoryHeaders + "\n\n" : "")
+ (criticalEventSection != null ? criticalEventSection : ""));
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index bbdaa24a694c..12fe6a0dba25 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -100,6 +100,7 @@ import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_LAST_OR
import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_FIRST_ORDERED_ID;
import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_LAST_ORDERED_ID;
import static com.android.server.wm.ActivityRecord.State.PAUSING;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ROOT_TASK;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH;
@@ -2011,7 +2012,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
return;
}
- if (r == mRootWindowContainer.getTopResumedActivity()) {
+ if (r.isState(RESUMED) && r == mRootWindowContainer.getTopResumedActivity()) {
setLastResumedActivityUncheckLocked(r, "setFocusedTask-alreadyTop");
return;
}
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 4be98a3c88b7..b4dffdcba243 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -48,7 +48,7 @@ import java.io.PrintWriter;
* Controller for IME inset source on the server. It's called provider as it provides the
* {@link InsetsSource} to the client that uses it in {@link InsetsSourceConsumer}.
*/
-final class ImeInsetsSourceProvider extends WindowContainerInsetsSourceProvider {
+final class ImeInsetsSourceProvider extends InsetsSourceProvider {
/** The token tracking the current IME request or {@code null} otherwise. */
@Nullable
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index a8c9cd30b656..fe13b87a079a 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -223,10 +223,10 @@ class InsetsPolicy {
startAnimation(false /* show */, () -> {
synchronized (mDisplayContent.mWmService.mGlobalLock) {
- final SparseArray<WindowContainerInsetsSourceProvider> providers =
+ final SparseArray<InsetsSourceProvider> providers =
mStateController.getSourceProviders();
for (int i = providers.size() - 1; i >= 0; i--) {
- final WindowContainerInsetsSourceProvider provider = providers.valueAt(i);
+ final InsetsSourceProvider provider = providers.valueAt(i);
if (!isTransient(provider.getSource().getType())) {
continue;
}
@@ -341,11 +341,10 @@ class InsetsPolicy {
}
}
- final SparseArray<WindowContainerInsetsSourceProvider> providers =
- mStateController.getSourceProviders();
+ final SparseArray<InsetsSourceProvider> providers = mStateController.getSourceProviders();
final int windowType = attrs.type;
for (int i = providers.size() - 1; i >= 0; i--) {
- final WindowContainerInsetsSourceProvider otherProvider = providers.valueAt(i);
+ final InsetsSourceProvider otherProvider = providers.valueAt(i);
if (otherProvider.overridesFrame(windowType)) {
if (state == originalState) {
state = new InsetsState(state);
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 0953604511d7..3b23f9717175 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -58,7 +58,7 @@ import java.util.function.Consumer;
* Controller for a specific inset source on the server. It's called provider as it provides the
* {@link InsetsSource} to the client that uses it in {@link android.view.InsetsSourceConsumer}.
*/
-abstract class InsetsSourceProvider {
+class InsetsSourceProvider {
protected final DisplayContent mDisplayContent;
protected final @NonNull InsetsSource mSource;
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index fca333d33731..249ead0a8509 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -56,7 +56,7 @@ class InsetsStateController {
private final InsetsState mState = new InsetsState();
private final DisplayContent mDisplayContent;
- private final SparseArray<WindowContainerInsetsSourceProvider> mProviders = new SparseArray<>();
+ private final SparseArray<InsetsSourceProvider> mProviders = new SparseArray<>();
private final ArrayMap<InsetsControlTarget, ArrayList<InsetsSourceProvider>>
mControlTargetProvidersMap = new ArrayMap<>();
private final SparseArray<InsetsControlTarget> mIdControlTargetMap = new SparseArray<>();
@@ -106,22 +106,22 @@ class InsetsStateController {
return result;
}
- SparseArray<WindowContainerInsetsSourceProvider> getSourceProviders() {
+ SparseArray<InsetsSourceProvider> getSourceProviders() {
return mProviders;
}
/**
* @return The provider of a specific source ID.
*/
- WindowContainerInsetsSourceProvider getOrCreateSourceProvider(int id, @InsetsType int type) {
- WindowContainerInsetsSourceProvider provider = mProviders.get(id);
+ InsetsSourceProvider getOrCreateSourceProvider(int id, @InsetsType int type) {
+ InsetsSourceProvider provider = mProviders.get(id);
if (provider != null) {
return provider;
}
final InsetsSource source = mState.getOrCreateSource(id, type);
provider = id == ID_IME
? new ImeInsetsSourceProvider(source, this, mDisplayContent)
- : new WindowContainerInsetsSourceProvider(source, this, mDisplayContent);
+ : new InsetsSourceProvider(source, this, mDisplayContent);
mProviders.put(id, provider);
return provider;
}
@@ -334,7 +334,7 @@ class InsetsStateController {
}
mDisplayContent.mWmService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
for (int i = mProviders.size() - 1; i >= 0; i--) {
- final WindowContainerInsetsSourceProvider provider = mProviders.valueAt(i);
+ final InsetsSourceProvider provider = mProviders.valueAt(i);
provider.onSurfaceTransactionApplied();
}
final ArraySet<InsetsControlTarget> newControlTargets = new ArraySet<>();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 2e217820c264..41176410a789 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -4122,7 +4122,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
private void hideInsetSourceViewOverflows() {
- final SparseArray<WindowContainerInsetsSourceProvider> providers =
+ final SparseArray<InsetsSourceProvider> providers =
getDisplayContent().getInsetsStateController().getSourceProviders();
for (int i = providers.size(); i >= 0; i--) {
final InsetsSourceProvider insetProvider = providers.valueAt(i);
diff --git a/services/core/java/com/android/server/wm/WindowContainerInsetsSourceProvider.java b/services/core/java/com/android/server/wm/WindowContainerInsetsSourceProvider.java
deleted file mode 100644
index aa2e8f541058..000000000000
--- a/services/core/java/com/android/server/wm/WindowContainerInsetsSourceProvider.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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.wm;
-
-import android.view.InsetsSource;
-
-/**
- * Controller for a specific inset source on the server. It's called provider as it provides the
- * {@link InsetsSource} to the client that uses it in {@link android.view.InsetsSourceConsumer}.
- */
-class WindowContainerInsetsSourceProvider extends InsetsSourceProvider {
- // TODO(b/218734524): Move the window container specific stuff from InsetsSourceProvider to
- // this class.
-
- WindowContainerInsetsSourceProvider(InsetsSource source,
- InsetsStateController stateController, DisplayContent displayContent) {
- super(source, stateController, displayContent);
- }
-}
-
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
index ef20f2b8fe64..b35eceb6dd11 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
@@ -42,20 +42,20 @@ import org.junit.runner.RunWith;
@SmallTest
@Presubmit
@RunWith(WindowTestRunner.class)
-public class WindowContainerInsetsSourceProviderTest extends WindowTestsBase {
+public class InsetsSourceProviderTest extends WindowTestsBase {
private InsetsSource mSource = new InsetsSource(
InsetsSource.createId(null, 0, statusBars()), statusBars());
- private WindowContainerInsetsSourceProvider mProvider;
+ private InsetsSourceProvider mProvider;
private InsetsSource mImeSource = new InsetsSource(ID_IME, ime());
- private WindowContainerInsetsSourceProvider mImeProvider;
+ private InsetsSourceProvider mImeProvider;
@Before
public void setUp() throws Exception {
mSource.setVisible(true);
- mProvider = new WindowContainerInsetsSourceProvider(mSource,
+ mProvider = new InsetsSourceProvider(mSource,
mDisplayContent.getInsetsStateController(), mDisplayContent);
- mImeProvider = new WindowContainerInsetsSourceProvider(mImeSource,
+ mImeProvider = new InsetsSourceProvider(mImeSource,
mDisplayContent.getInsetsStateController(), mDisplayContent);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 74fde65c4dcd..ff2944a80976 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -287,7 +287,7 @@ public class InsetsStateControllerTest extends WindowTestsBase {
// IME cannot be the IME target.
ime.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
- WindowContainerInsetsSourceProvider statusBarProvider =
+ InsetsSourceProvider statusBarProvider =
getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars());
final SparseArray<TriConsumer<DisplayFrames, WindowContainer, Rect>> imeOverrideProviders =
new SparseArray<>();
@@ -353,7 +353,7 @@ public class InsetsStateControllerTest extends WindowTestsBase {
public void testTransientVisibilityOfFixedRotationState() {
final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
- final WindowContainerInsetsSourceProvider provider = getController()
+ final InsetsSourceProvider provider = getController()
.getOrCreateSourceProvider(ID_STATUS_BAR, statusBars());
provider.setWindowContainer(statusBar, null, null);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 6261e56a87c5..a1ddd5748002 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -484,7 +484,7 @@ public class WindowContainerTests extends WindowTestsBase {
windowState.mSurfaceAnimator).getAnimationType();
assertTrue(parent.isAnimating(CHILDREN));
- windowState.setControllableInsetProvider(mock(WindowContainerInsetsSourceProvider.class));
+ windowState.setControllableInsetProvider(mock(InsetsSourceProvider.class));
assertFalse(parent.isAnimating(CHILDREN));
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 48a39e682340..f3cb9baedd4b 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -74,9 +74,8 @@ import com.android.server.voiceinteraction.VoiceInteractionManagerServiceImpl.De
import java.io.PrintWriter;
import java.time.Instant;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -122,8 +121,8 @@ final class HotwordDetectionConnection {
private static final int DETECTION_SERVICE_TYPE_VISUAL_QUERY = 2;
// TODO: This may need to be a Handler(looper)
- private final ScheduledExecutorService mScheduledExecutorService =
- Executors.newSingleThreadScheduledExecutor();
+ private final ScheduledThreadPoolExecutor mScheduledExecutorService =
+ new ScheduledThreadPoolExecutor(1);
@Nullable private final ScheduledFuture<?> mCancellationTaskFuture;
private final IBinder.DeathRecipient mAudioServerDeathRecipient = this::audioServerDied;
@NonNull private final ServiceConnectionFactory mHotwordDetectionServiceConnectionFactory;
@@ -210,6 +209,7 @@ final class HotwordDetectionConnection {
if (mReStartPeriodSeconds <= 0) {
mCancellationTaskFuture = null;
} else {
+ mScheduledExecutorService.setRemoveOnCancelPolicy(true);
// TODO: we need to be smarter here, e.g. schedule it a bit more often,
// but wait until the current session is closed.
mCancellationTaskFuture = mScheduledExecutorService.scheduleAtFixedRate(() -> {