diff options
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 Binary files differnew file mode 100644 index 000000000000..68473ba6c962 --- /dev/null +++ b/packages/SystemUI/tests/res/drawable-nodpi/romainguy_rockaway.jpg 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(() -> { |