diff options
170 files changed, 1715 insertions, 3591 deletions
diff --git a/apex/jobscheduler/OWNERS b/apex/jobscheduler/OWNERS index 58434f165aa2..22b648975e5f 100644 --- a/apex/jobscheduler/OWNERS +++ b/apex/jobscheduler/OWNERS @@ -2,7 +2,6 @@ ctate@android.com ctate@google.com dplotnikov@google.com jji@google.com -kwekua@google.com omakoto@google.com suprabh@google.com varunshah@google.com diff --git a/cmds/incident_helper/OWNERS b/cmds/incident_helper/OWNERS index cede4eae50ad..29f44aba75d1 100644 --- a/cmds/incident_helper/OWNERS +++ b/cmds/incident_helper/OWNERS @@ -1,3 +1,2 @@ joeo@google.com -kwekua@google.com yanmin@google.com diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index 75485626d2ef..508077ed43cc 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -2655,13 +2655,12 @@ public class AssistStructure implements Parcelable { ); } GetCredentialRequest getCredentialRequest = node.getPendingCredentialRequest(); - if (getCredentialRequest == null) { - Log.i(TAG, prefix + " No Credential Manager Request"); - } else { - Log.i(TAG, prefix + " GetCredentialRequest: no. of options= " - + getCredentialRequest.getCredentialOptions().size() - ); - } + Log.i(TAG, prefix + " Credential Manager info:" + + " hasCredentialManagerRequest=" + (getCredentialRequest != null) + + (getCredentialRequest != null + ? ", sizeOfOptions=" + getCredentialRequest.getCredentialOptions().size() + : "") + ); final int NCHILDREN = node.getChildCount(); if (NCHILDREN > 0) { diff --git a/core/java/com/android/internal/compat/Android.bp b/core/java/com/android/internal/compat/Android.bp index 9ff05a6589b7..8253aa89b979 100644 --- a/core/java/com/android/internal/compat/Android.bp +++ b/core/java/com/android/internal/compat/Android.bp @@ -1,6 +1,7 @@ aconfig_declarations { name: "compat_logging_flags", package: "com.android.internal.compat.flags", + container: "system", srcs: [ "compat_logging_flags.aconfig", ], diff --git a/core/java/com/android/internal/compat/compat_logging_flags.aconfig b/core/java/com/android/internal/compat/compat_logging_flags.aconfig index a5c31edde473..4f5162693408 100644 --- a/core/java/com/android/internal/compat/compat_logging_flags.aconfig +++ b/core/java/com/android/internal/compat/compat_logging_flags.aconfig @@ -1,4 +1,5 @@ package: "com.android.internal.compat.flags" +container: "system" flag { name: "skip_old_and_disabled_compat_logging" @@ -6,4 +7,4 @@ flag { description: "Feature flag for skipping debug logging for changes that do not target the latest sdk or are disabled" bug: "323949942" is_fixed_read_only: true -}
\ No newline at end of file +} diff --git a/core/java/com/android/internal/foldables/Android.bp b/core/java/com/android/internal/foldables/Android.bp index f1d06da98186..53a9be5fdb0e 100644 --- a/core/java/com/android/internal/foldables/Android.bp +++ b/core/java/com/android/internal/foldables/Android.bp @@ -1,6 +1,7 @@ aconfig_declarations { name: "fold_lock_setting_flags", package: "com.android.internal.foldables.flags", + container: "system", srcs: [ "fold_lock_setting_flags.aconfig", ], diff --git a/core/java/com/android/internal/foldables/fold_lock_setting_flags.aconfig b/core/java/com/android/internal/foldables/fold_lock_setting_flags.aconfig index d73e62373732..03b6a5bcf467 100644 --- a/core/java/com/android/internal/foldables/fold_lock_setting_flags.aconfig +++ b/core/java/com/android/internal/foldables/fold_lock_setting_flags.aconfig @@ -1,4 +1,5 @@ package: "com.android.internal.foldables.flags" +container: "system" flag { name: "fold_lock_setting_enabled" diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp index 7c976b7473f6..b6bf617b40ae 100644 --- a/core/jni/android_text_Hyphenator.cpp +++ b/core/jni/android_text_Hyphenator.cpp @@ -36,43 +36,41 @@ static std::string buildFileName(const std::string& locale) { return SYSTEM_HYPHENATOR_PREFIX + lowerLocale + SYSTEM_HYPHENATOR_SUFFIX; } -static std::pair<const uint8_t*, uint32_t> mmapPatternFile(const std::string& locale) { +static const uint8_t* mmapPatternFile(const std::string& locale) { const std::string hyFilePath = buildFileName(locale); const int fd = open(hyFilePath.c_str(), O_RDONLY | O_CLOEXEC); if (fd == -1) { - return std::make_pair(nullptr, 0); // Open failed. + return nullptr; // Open failed. } struct stat st = {}; if (fstat(fd, &st) == -1) { // Unlikely to happen. close(fd); - return std::make_pair(nullptr, 0); + return nullptr; } void* ptr = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0 /* offset */); close(fd); if (ptr == MAP_FAILED) { - return std::make_pair(nullptr, 0); + return nullptr; } - return std::make_pair(reinterpret_cast<const uint8_t*>(ptr), st.st_size); + return reinterpret_cast<const uint8_t*>(ptr); } static void addHyphenatorWithoutPatternFile(const std::string& locale, int minPrefix, int minSuffix) { - minikin::addHyphenator(locale, - minikin::Hyphenator::loadBinary(nullptr, 0, minPrefix, minSuffix, - locale)); + minikin::addHyphenator(locale, minikin::Hyphenator::loadBinary( + nullptr, minPrefix, minSuffix, locale)); } static void addHyphenator(const std::string& locale, int minPrefix, int minSuffix) { - auto [ptr, size] = mmapPatternFile(locale); + const uint8_t* ptr = mmapPatternFile(locale); if (ptr == nullptr) { ALOGE("Unable to find pattern file or unable to map it for %s", locale.c_str()); return; } - minikin::addHyphenator(locale, - minikin::Hyphenator::loadBinary(ptr, size, minPrefix, minSuffix, - locale)); + minikin::addHyphenator(locale, minikin::Hyphenator::loadBinary( + ptr, minPrefix, minSuffix, locale)); } static void addHyphenatorAlias(const std::string& from, const std::string& to) { diff --git a/core/proto/OWNERS b/core/proto/OWNERS index b900fa60ff70..b51f72dee260 100644 --- a/core/proto/OWNERS +++ b/core/proto/OWNERS @@ -11,7 +11,6 @@ zhouwenjie@google.com # Frameworks ogunwale@google.com jjaggi@google.com -kwekua@google.com roosa@google.com per-file package_item_info.proto = file:/PACKAGE_MANAGER_OWNERS per-file usagestatsservice.proto, usagestatsservice_v2.proto = file:/core/java/android/app/usage/OWNERS diff --git a/graphics/java/Android.bp b/graphics/java/Android.bp index ece453d1a70e..f4abd0ab582c 100644 --- a/graphics/java/Android.bp +++ b/graphics/java/Android.bp @@ -11,6 +11,7 @@ package { aconfig_declarations { name: "framework_graphics_flags", package: "com.android.graphics.flags", + container: "system", srcs: ["android/framework_graphics.aconfig"], } diff --git a/graphics/java/android/framework_graphics.aconfig b/graphics/java/android/framework_graphics.aconfig index 1e41b4d9ed1b..4ab09eb7e9f8 100644 --- a/graphics/java/android/framework_graphics.aconfig +++ b/graphics/java/android/framework_graphics.aconfig @@ -1,4 +1,5 @@ package: "com.android.graphics.flags" +container: "system" flag { name: "exact_compute_bounds" @@ -14,4 +15,4 @@ flag { namespace: "core_graphics" description: "Feature flag for YUV image compress to Ultra HDR." bug: "308978825" -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index a541c590575f..c2ba064ac7b6 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -45,9 +45,6 @@ <!-- Allow PIP to resize to a slightly bigger state upon touch/showing the menu --> <bool name="config_pipEnableResizeForMenu">true</bool> - <!-- PiP minimum size, which is a % based off the shorter side of display width and height --> - <fraction name="config_pipShortestEdgePercent">40%</fraction> - <!-- Time (duration in milliseconds) that the shell waits for an app to close the PiP by itself if a custom action is present before closing it. --> <integer name="config_pipForceCloseDelay">1000</integer> @@ -91,11 +88,45 @@ 16x16 </string> + <!-- Default percentages for the PIP size logic. + 1. Determine max widths + Subtract width of system UI and default padding from the shortest edge of the device. + This is the max width. + 2. Calculate Default and Mins + Default is config_pipSystemPreferredDefaultSizePercent of max-width/height. + Min is config_pipSystemPreferredMinimumSizePercent of it. --> + <item name="config_pipSystemPreferredDefaultSizePercent" format="float" type="dimen">0.6</item> + <item name="config_pipSystemPreferredMinimumSizePercent" format="float" type="dimen">0.5</item> + <!-- Default percentages for the PIP size logic when the Display is close to square. + This is used instead when the display is square-ish, like fold-ables when unfolded, + to make sure that default PiP does not cover the hinge (halfway of the display). + 0. Determine if the display is square-ish + If min(displayWidth, displayHeight) / max(displayWidth, displayHeight) is greater than + config_pipSquareDisplayThresholdForSystemPreferredSize, we use the percent for + square display listed below. + 1. Determine max widths + Subtract width of system UI and default padding from the shortest edge of the device. + This is the max width. + 2. Calculate Default and Mins + Default is config_pipSystemPreferredDefaultSizePercentForSquareDisplay of max-width/height. + Min is config_pipSystemPreferredMinimumSizePercentForSquareDisplay of it. --> + <item name="config_pipSquareDisplayThresholdForSystemPreferredSize" + format="float" type="dimen">0.95</item> + <item name="config_pipSystemPreferredDefaultSizePercentForSquareDisplay" + format="float" type="dimen">0.5</item> + <item name="config_pipSystemPreferredMinimumSizePercentForSquareDisplay" + format="float" type="dimen">0.4</item> + <!-- The percentage of the screen width to use for the default width or height of picture-in-picture windows. Regardless of the percent set here, calculated size will never - be smaller than @dimen/default_minimal_size_pip_resizable_task. --> + be smaller than @dimen/default_minimal_size_pip_resizable_task. + This is used in legacy spec, use config_pipSystemPreferredDefaultSizePercent instead. --> <item name="config_pictureInPictureDefaultSizePercent" format="float" type="dimen">0.23</item> + <!-- PiP minimum size, which is a % based off the shorter side of display width and height. + This is used in legacy spec, use config_pipSystemPreferredMinimumSizePercent instead. --> + <fraction name="config_pipShortestEdgePercent">40%</fraction> + <!-- The default aspect ratio for picture-in-picture windows. --> <item name="config_pictureInPictureDefaultAspectRatio" format="float" type="dimen"> 1.777778 diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 0867a44c1998..313d0d24b459 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -2335,6 +2335,11 @@ public class BubbleController implements ConfigurationChangeListener, mMainExecutor.execute(() -> mController.setBubbleBarLocation(location)); } + + @Override + public void setBubbleBarBounds(Rect bubbleBarBounds) { + mMainExecutor.execute(() -> mBubblePositioner.setBubbleBarBounds(bubbleBarBounds)); + } } private class BubblesImpl implements Bubbles { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl index 16134d3e5a70..c9f0f0d61713 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl @@ -44,4 +44,6 @@ interface IBubbles { oneway void showUserEducation(in int positionX, in int positionY) = 8; oneway void setBubbleBarLocation(in BubbleBarLocation location) = 9; + + oneway void setBubbleBarBounds(in Rect bubbleBarBounds) = 10; }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java index 8af4c75b5733..45ad6319bbf8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java @@ -166,13 +166,8 @@ public class BubbleBarAnimationHelper { bbev.setTaskViewAlpha(0f); bbev.setVisibility(VISIBLE); - // Set the pivot point for the scale, so the view animates out from the bubble bar. - Rect bubbleBarBounds = mPositioner.getBubbleBarBounds(); - mExpandedViewContainerMatrix.setScale( - 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT, - 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT, - bubbleBarBounds.centerX(), - bubbleBarBounds.top); + setScaleFromBubbleBar(mExpandedViewContainerMatrix, + 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT); bbev.setAnimationMatrix(mExpandedViewContainerMatrix); @@ -214,8 +209,8 @@ public class BubbleBarAnimationHelper { } bbev.setScaleX(1f); bbev.setScaleY(1f); - mExpandedViewContainerMatrix.setScaleX(1f); - mExpandedViewContainerMatrix.setScaleY(1f); + + setScaleFromBubbleBar(mExpandedViewContainerMatrix, 1f); PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel(); PhysicsAnimator.getInstance(mExpandedViewContainerMatrix) @@ -240,6 +235,16 @@ public class BubbleBarAnimationHelper { mExpandedViewAlphaAnimator.reverse(); } + private void setScaleFromBubbleBar(AnimatableScaleMatrix matrix, float scale) { + // Set the pivot point for the scale, so the view animates out from the bubble bar. + Rect bubbleBarBounds = mPositioner.getBubbleBarBounds(); + matrix.setScale( + scale, + scale, + bubbleBarBounds.centerX(), + bubbleBarBounds.top); + } + /** * Animate the expanded bubble when it is being dragged */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt index 18c7bdd6d5ba..7eb0f267b312 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt @@ -18,7 +18,6 @@ package com.android.wm.shell.common.pip import android.content.Context import android.content.res.Resources -import android.os.SystemProperties import android.util.Size import com.android.wm.shell.R import java.io.PrintWriter @@ -36,30 +35,81 @@ class PhoneSizeSpecSource( private var mOverrideMinSize: Size? = null - /** Default and minimum percentages for the PIP size logic. */ - private val mDefaultSizePercent: Float - private val mMinimumSizePercent: Float + /** + * Default percentages for the PIP size logic. + * 1. Determine max widths + * Subtract width of system UI and default padding from the shortest edge of the device. + * This is the max width. + * 2. Calculate Default and Mins + * Default is mSystemPreferredDefaultSizePercent of max-width/height. + * Min is mSystemPreferredMinimumSizePercent of it. + * + * NOTE: Do not use this directly, use the mPreferredDefaultSizePercent getter instead. + */ + private var mSystemPreferredDefaultSizePercent = 0.6f + /** Minimum percentages for the PIP size logic. */ + private var mSystemPreferredMinimumSizePercent = 0.5f + + /** Threshold to categorize the Display as square, calculated as min(w, h) / max(w, h). */ + private var mSquareDisplayThresholdForSystemPreferredSize = 0.95f + /** + * Default percentages for the PIP size logic when the Display is square-ish. + * This is used instead when the display is square-ish, like fold-ables when unfolded, + * to make sure that default PiP does not cover the hinge (halfway of the display). + * 1. Determine max widths + * Subtract width of system UI and default padding from the shortest edge of the device. + * This is the max width. + * 2. Calculate Default and Mins + * Default is mSystemPreferredDefaultSizePercent of max-width/height. + * Min is mSystemPreferredMinimumSizePercent of it. + * + * NOTE: Do not use this directly, use the mPreferredDefaultSizePercent getter instead. + */ + private var mSystemPreferredDefaultSizePercentForSquareDisplay = 0.5f + /** Minimum percentages for the PIP size logic. */ + private var mSystemPreferredMinimumSizePercentForSquareDisplay = 0.4f + + private val mIsSquareDisplay + get() = minOf(pipDisplayLayoutState.displayLayout.width(), + pipDisplayLayoutState.displayLayout.height()).toFloat() / + maxOf(pipDisplayLayoutState.displayLayout.width(), + pipDisplayLayoutState.displayLayout.height()) > + mSquareDisplayThresholdForSystemPreferredSize + private val mPreferredDefaultSizePercent + get() = if (mIsSquareDisplay) mSystemPreferredDefaultSizePercentForSquareDisplay else + mSystemPreferredDefaultSizePercent + + private val mPreferredMinimumSizePercent + get() = if (mIsSquareDisplay) mSystemPreferredMinimumSizePercentForSquareDisplay else + mSystemPreferredMinimumSizePercent /** Aspect ratio that the PIP size spec logic optimizes for. */ private var mOptimizedAspectRatio = 0f init { - mDefaultSizePercent = SystemProperties - .get("com.android.wm.shell.pip.phone.def_percentage", "0.6").toFloat() - mMinimumSizePercent = SystemProperties - .get("com.android.wm.shell.pip.phone.min_percentage", "0.5").toFloat() - reloadResources() } private fun reloadResources() { - val res: Resources = context.getResources() + val res: Resources = context.resources mDefaultMinSize = res.getDimensionPixelSize( R.dimen.default_minimal_size_pip_resizable_task) mOverridableMinSize = res.getDimensionPixelSize( R.dimen.overridable_minimal_size_pip_resizable_task) + mSystemPreferredDefaultSizePercent = res.getFloat( + R.dimen.config_pipSystemPreferredDefaultSizePercent) + mSystemPreferredMinimumSizePercent = res.getFloat( + R.dimen.config_pipSystemPreferredMinimumSizePercent) + + mSquareDisplayThresholdForSystemPreferredSize = res.getFloat( + R.dimen.config_pipSquareDisplayThresholdForSystemPreferredSize) + mSystemPreferredDefaultSizePercentForSquareDisplay = res.getFloat( + R.dimen.config_pipSystemPreferredDefaultSizePercentForSquareDisplay) + mSystemPreferredMinimumSizePercentForSquareDisplay = res.getFloat( + R.dimen.config_pipSystemPreferredMinimumSizePercentForSquareDisplay) + val requestedOptAspRatio = res.getFloat(R.dimen.config_pipLargeScreenOptimizedAspectRatio) // make sure the optimized aspect ratio is valid with a default value to fall back to mOptimizedAspectRatio = if (requestedOptAspRatio > 1) { @@ -128,7 +178,7 @@ class PhoneSizeSpecSource( return minSize } val maxSize = getMaxSize(aspectRatio) - val defaultWidth = Math.max(Math.round(maxSize.width * mDefaultSizePercent), + val defaultWidth = Math.max(Math.round(maxSize.width * mPreferredDefaultSizePercent), minSize.width) val defaultHeight = Math.round(defaultWidth / aspectRatio) return Size(defaultWidth, defaultHeight) @@ -146,8 +196,8 @@ class PhoneSizeSpecSource( return adjustOverrideMinSizeToAspectRatio(aspectRatio)!! } val maxSize = getMaxSize(aspectRatio) - var minWidth = Math.round(maxSize.width * mMinimumSizePercent) - var minHeight = Math.round(maxSize.height * mMinimumSizePercent) + var minWidth = Math.round(maxSize.width * mPreferredMinimumSizePercent) + var minHeight = Math.round(maxSize.height * mPreferredMinimumSizePercent) // make sure the calculated min size is not smaller than the allowed default min size if (aspectRatio > 1f) { @@ -244,8 +294,8 @@ class PhoneSizeSpecSource( pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize) pw.println(innerPrefix + "mOverridableMinSize=" + mOverridableMinSize) pw.println(innerPrefix + "mDefaultMinSize=" + mDefaultMinSize) - pw.println(innerPrefix + "mDefaultSizePercent=" + mDefaultSizePercent) - pw.println(innerPrefix + "mMinimumSizePercent=" + mMinimumSizePercent) + pw.println(innerPrefix + "mDefaultSizePercent=" + mPreferredDefaultSizePercent) + pw.println(innerPrefix + "mMinimumSizePercent=" + mPreferredMinimumSizePercent) pw.println(innerPrefix + "mOptimizedAspectRatio=" + mOptimizedAspectRatio) } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java index 3d5cd6939d1b..85f1da5322ea 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java @@ -16,33 +16,26 @@ package com.android.wm.shell.pip.phone; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; - -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; import android.content.Context; import android.content.res.Resources; -import android.os.SystemProperties; import android.testing.AndroidTestingRunner; import android.util.Size; import android.view.DisplayInfo; -import com.android.dx.mockito.inline.extended.StaticMockitoSession; +import com.android.wm.shell.R; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.pip.PhoneSizeSpecSource; import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.common.pip.SizeSpecSource; -import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.exceptions.misusing.InvalidUseOfMatchersException; import java.util.HashMap; import java.util.Map; @@ -63,15 +56,24 @@ public class PhoneSizeSpecSourceTest extends ShellTestCase { private static final float DEFAULT_PERCENT = 0.6f; /** Minimum sizing percentage */ private static final float MIN_PERCENT = 0.5f; + /** Threshold to determine if a Display is square-ish. */ + private static final float SQUARE_DISPLAY_THRESHOLD = 0.95f; + /** Default sizing percentage for square-ish Display. */ + private static final float SQUARE_DISPLAY_DEFAULT_PERCENT = 0.5f; + /** Minimum sizing percentage for square-ish Display. */ + private static final float SQUARE_DISPLAY_MIN_PERCENT = 0.4f; /** Aspect ratio that the new PIP size spec logic optimizes for. */ private static final float OPTIMIZED_ASPECT_RATIO = 9f / 16; - /** A map of aspect ratios to be tested to expected sizes */ - private static Map<Float, Size> sExpectedMaxSizes; - private static Map<Float, Size> sExpectedDefaultSizes; - private static Map<Float, Size> sExpectedMinSizes; - /** A static mockito session object to mock {@link SystemProperties} */ - private static StaticMockitoSession sStaticMockitoSession; + /** Maps of aspect ratios to be tested to expected sizes on non-square Display. */ + private static Map<Float, Size> sNonSquareDisplayExpectedMaxSizes; + private static Map<Float, Size> sNonSquareDisplayExpectedDefaultSizes; + private static Map<Float, Size> sNonSquareDisplayExpectedMinSizes; + + /** Maps of aspect ratios to be tested to expected sizes on square Display. */ + private static Map<Float, Size> sSquareDisplayExpectedMaxSizes; + private static Map<Float, Size> sSquareDisplayExpectedDefaultSizes; + private static Map<Float, Size> sSquareDisplayExpectedMinSizes; @Mock private Context mContext; @Mock private Resources mResources; @@ -80,49 +82,55 @@ public class PhoneSizeSpecSourceTest extends ShellTestCase { private SizeSpecSource mSizeSpecSource; /** - * Sets up static Mockito session for SystemProperties and mocks necessary static methods. + * Initializes the map with the aspect ratios to be tested and corresponding expected max sizes. + * This is to initialize the expectations on non-square Display only. */ - private static void setUpStaticSystemPropertiesSession() { - sStaticMockitoSession = mockitoSession() - .mockStatic(SystemProperties.class).startMocking(); - when(SystemProperties.get(anyString(), anyString())).thenAnswer(invocation -> { - String property = invocation.getArgument(0); - if (property.equals("com.android.wm.shell.pip.phone.def_percentage")) { - return Float.toString(DEFAULT_PERCENT); - } else if (property.equals("com.android.wm.shell.pip.phone.min_percentage")) { - return Float.toString(MIN_PERCENT); - } - - // throw an exception if illegal arguments are used for these tests - throw new InvalidUseOfMatchersException( - String.format("Argument %s does not match", property) - ); - }); + private static void initNonSquareDisplayExpectedSizes() { + sNonSquareDisplayExpectedMaxSizes = new HashMap<>(); + sNonSquareDisplayExpectedDefaultSizes = new HashMap<>(); + sNonSquareDisplayExpectedMinSizes = new HashMap<>(); + + sNonSquareDisplayExpectedMaxSizes.put(16f / 9, new Size(1000, 563)); + sNonSquareDisplayExpectedDefaultSizes.put(16f / 9, new Size(600, 338)); + sNonSquareDisplayExpectedMinSizes.put(16f / 9, new Size(501, 282)); + + sNonSquareDisplayExpectedMaxSizes.put(4f / 3, new Size(893, 670)); + sNonSquareDisplayExpectedDefaultSizes.put(4f / 3, new Size(536, 402)); + sNonSquareDisplayExpectedMinSizes.put(4f / 3, new Size(447, 335)); + + sNonSquareDisplayExpectedMaxSizes.put(3f / 4, new Size(670, 893)); + sNonSquareDisplayExpectedDefaultSizes.put(3f / 4, new Size(402, 536)); + sNonSquareDisplayExpectedMinSizes.put(3f / 4, new Size(335, 447)); + + sNonSquareDisplayExpectedMaxSizes.put(9f / 16, new Size(563, 1001)); + sNonSquareDisplayExpectedDefaultSizes.put(9f / 16, new Size(338, 601)); + sNonSquareDisplayExpectedMinSizes.put(9f / 16, new Size(282, 501)); } /** * Initializes the map with the aspect ratios to be tested and corresponding expected max sizes. + * This is to initialize the expectations on square Display only. */ - private static void initExpectedSizes() { - sExpectedMaxSizes = new HashMap<>(); - sExpectedDefaultSizes = new HashMap<>(); - sExpectedMinSizes = new HashMap<>(); - - sExpectedMaxSizes.put(16f / 9, new Size(1000, 563)); - sExpectedDefaultSizes.put(16f / 9, new Size(600, 338)); - sExpectedMinSizes.put(16f / 9, new Size(501, 282)); - - sExpectedMaxSizes.put(4f / 3, new Size(893, 670)); - sExpectedDefaultSizes.put(4f / 3, new Size(536, 402)); - sExpectedMinSizes.put(4f / 3, new Size(447, 335)); - - sExpectedMaxSizes.put(3f / 4, new Size(670, 893)); - sExpectedDefaultSizes.put(3f / 4, new Size(402, 536)); - sExpectedMinSizes.put(3f / 4, new Size(335, 447)); - - sExpectedMaxSizes.put(9f / 16, new Size(563, 1001)); - sExpectedDefaultSizes.put(9f / 16, new Size(338, 601)); - sExpectedMinSizes.put(9f / 16, new Size(282, 501)); + private static void initSquareDisplayExpectedSizes() { + sSquareDisplayExpectedMaxSizes = new HashMap<>(); + sSquareDisplayExpectedDefaultSizes = new HashMap<>(); + sSquareDisplayExpectedMinSizes = new HashMap<>(); + + sSquareDisplayExpectedMaxSizes.put(16f / 9, new Size(1000, 563)); + sSquareDisplayExpectedDefaultSizes.put(16f / 9, new Size(500, 281)); + sSquareDisplayExpectedMinSizes.put(16f / 9, new Size(400, 225)); + + sSquareDisplayExpectedMaxSizes.put(4f / 3, new Size(893, 670)); + sSquareDisplayExpectedDefaultSizes.put(4f / 3, new Size(447, 335)); + sSquareDisplayExpectedMinSizes.put(4f / 3, new Size(357, 268)); + + sSquareDisplayExpectedMaxSizes.put(3f / 4, new Size(670, 893)); + sSquareDisplayExpectedDefaultSizes.put(3f / 4, new Size(335, 447)); + sSquareDisplayExpectedMinSizes.put(3f / 4, new Size(268, 357)); + + sSquareDisplayExpectedMaxSizes.put(9f / 16, new Size(563, 1001)); + sSquareDisplayExpectedDefaultSizes.put(9f / 16, new Size(282, 501)); + sSquareDisplayExpectedMinSizes.put(9f / 16, new Size(225, 400)); } private void forEveryTestCaseCheck(Map<Float, Size> expectedSizes, @@ -137,20 +145,38 @@ public class PhoneSizeSpecSourceTest extends ShellTestCase { @Before public void setUp() { - initExpectedSizes(); - - when(mResources.getDimensionPixelSize(anyInt())).thenReturn(DEFAULT_MIN_EDGE_SIZE); - when(mResources.getFloat(anyInt())).thenReturn(OPTIMIZED_ASPECT_RATIO); - when(mResources.getString(anyInt())).thenReturn("0x0"); + initNonSquareDisplayExpectedSizes(); + initSquareDisplayExpectedSizes(); + + when(mResources.getFloat(R.dimen.config_pipSystemPreferredDefaultSizePercent)) + .thenReturn(DEFAULT_PERCENT); + when(mResources.getFloat(R.dimen.config_pipSystemPreferredMinimumSizePercent)) + .thenReturn(MIN_PERCENT); + when(mResources.getDimensionPixelSize(R.dimen.default_minimal_size_pip_resizable_task)) + .thenReturn(DEFAULT_MIN_EDGE_SIZE); + when(mResources.getFloat(R.dimen.config_pipLargeScreenOptimizedAspectRatio)) + .thenReturn(OPTIMIZED_ASPECT_RATIO); + when(mResources.getString(R.string.config_defaultPictureInPictureScreenEdgeInsets)) + .thenReturn("0x0"); when(mResources.getDisplayMetrics()) .thenReturn(getContext().getResources().getDisplayMetrics()); + when(mResources.getFloat(R.dimen.config_pipSquareDisplayThresholdForSystemPreferredSize)) + .thenReturn(SQUARE_DISPLAY_THRESHOLD); + when(mResources.getFloat( + R.dimen.config_pipSystemPreferredDefaultSizePercentForSquareDisplay)) + .thenReturn(SQUARE_DISPLAY_DEFAULT_PERCENT); + when(mResources.getFloat( + R.dimen.config_pipSystemPreferredMinimumSizePercentForSquareDisplay)) + .thenReturn(SQUARE_DISPLAY_MIN_PERCENT); // set up the mock context for spec handler specifically when(mContext.getResources()).thenReturn(mResources); + } + private void setupSizeSpecWithDisplayDimension(int width, int height) { DisplayInfo displayInfo = new DisplayInfo(); - displayInfo.logicalWidth = DISPLAY_EDGE_SIZE; - displayInfo.logicalHeight = DISPLAY_EDGE_SIZE; + displayInfo.logicalWidth = width; + displayInfo.logicalHeight = height; // use the parent context (not the mocked one) to obtain the display layout // this is done to avoid unnecessary mocking while allowing for custom display dimensions @@ -159,38 +185,57 @@ public class PhoneSizeSpecSourceTest extends ShellTestCase { mPipDisplayLayoutState = new PipDisplayLayoutState(mContext); mPipDisplayLayoutState.setDisplayLayout(displayLayout); - setUpStaticSystemPropertiesSession(); mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState); // no overridden min edge size by default mSizeSpecSource.setOverrideMinSize(null); } - @After - public void cleanUp() { - sStaticMockitoSession.finishMocking(); + @Test + public void testGetMaxSize_nonSquareDisplay() { + setupSizeSpecWithDisplayDimension(DISPLAY_EDGE_SIZE * 2, DISPLAY_EDGE_SIZE); + forEveryTestCaseCheck(sNonSquareDisplayExpectedMaxSizes, + (aspectRatio) -> mSizeSpecSource.getMaxSize(aspectRatio)); + } + + @Test + public void testGetDefaultSize_nonSquareDisplay() { + setupSizeSpecWithDisplayDimension(DISPLAY_EDGE_SIZE * 2, DISPLAY_EDGE_SIZE); + forEveryTestCaseCheck(sNonSquareDisplayExpectedDefaultSizes, + (aspectRatio) -> mSizeSpecSource.getDefaultSize(aspectRatio)); + } + + @Test + public void testGetMinSize_nonSquareDisplay() { + setupSizeSpecWithDisplayDimension(DISPLAY_EDGE_SIZE * 2, DISPLAY_EDGE_SIZE); + forEveryTestCaseCheck(sNonSquareDisplayExpectedMinSizes, + (aspectRatio) -> mSizeSpecSource.getMinSize(aspectRatio)); } @Test - public void testGetMaxSize() { - forEveryTestCaseCheck(sExpectedMaxSizes, + public void testGetMaxSize_squareDisplay() { + setupSizeSpecWithDisplayDimension(DISPLAY_EDGE_SIZE, DISPLAY_EDGE_SIZE); + forEveryTestCaseCheck(sSquareDisplayExpectedMaxSizes, (aspectRatio) -> mSizeSpecSource.getMaxSize(aspectRatio)); } @Test - public void testGetDefaultSize() { - forEveryTestCaseCheck(sExpectedDefaultSizes, + public void testGetDefaultSize_squareDisplay() { + setupSizeSpecWithDisplayDimension(DISPLAY_EDGE_SIZE, DISPLAY_EDGE_SIZE); + forEveryTestCaseCheck(sSquareDisplayExpectedDefaultSizes, (aspectRatio) -> mSizeSpecSource.getDefaultSize(aspectRatio)); } @Test - public void testGetMinSize() { - forEveryTestCaseCheck(sExpectedMinSizes, + public void testGetMinSize_squareDisplay() { + setupSizeSpecWithDisplayDimension(DISPLAY_EDGE_SIZE, DISPLAY_EDGE_SIZE); + forEveryTestCaseCheck(sSquareDisplayExpectedMinSizes, (aspectRatio) -> mSizeSpecSource.getMinSize(aspectRatio)); } @Test public void testGetSizeForAspectRatio_noOverrideMinSize() { + setupSizeSpecWithDisplayDimension(DISPLAY_EDGE_SIZE * 2, DISPLAY_EDGE_SIZE); // an initial size with 16:9 aspect ratio Size initSize = new Size(600, 337); @@ -202,6 +247,7 @@ public class PhoneSizeSpecSourceTest extends ShellTestCase { @Test public void testGetSizeForAspectRatio_withOverrideMinSize() { + setupSizeSpecWithDisplayDimension(DISPLAY_EDGE_SIZE * 2, DISPLAY_EDGE_SIZE); // an initial size with a 1:1 aspect ratio Size initSize = new Size(OVERRIDE_MIN_EDGE_SIZE, OVERRIDE_MIN_EDGE_SIZE); mSizeSpecSource.setOverrideMinSize(initSize); diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 341c3e8cf373..02fa5f2608ab 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -31,6 +31,7 @@ license { aconfig_declarations { name: "hwui_flags", package: "com.android.graphics.hwui.flags", + container: "system", srcs: [ "aconfig/hwui_flags.aconfig", ], diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig index 659bcdc6852d..50f8b3929e1e 100644 --- a/libs/hwui/aconfig/hwui_flags.aconfig +++ b/libs/hwui/aconfig/hwui_flags.aconfig @@ -1,4 +1,5 @@ package: "com.android.graphics.hwui.flags" +container: "system" flag { name: "clip_shader" diff --git a/media/java/android/media/FadeManagerConfiguration.java b/media/java/android/media/FadeManagerConfiguration.java index eaafa595ae4f..6d84e7066839 100644 --- a/media/java/android/media/FadeManagerConfiguration.java +++ b/media/java/android/media/FadeManagerConfiguration.java @@ -836,7 +836,7 @@ public final class FadeManagerConfiguration implements Parcelable { */ public Builder(@NonNull FadeManagerConfiguration fmc) { mFadeState = fmc.mFadeState; - mUsageToFadeWrapperMap = fmc.mUsageToFadeWrapperMap.clone(); + copyUsageToFadeWrapperMapInternal(fmc.mUsageToFadeWrapperMap); mAttrToFadeWrapperMap = new ArrayMap<AudioAttributes, FadeVolumeShaperConfigsWrapper>( fmc.mAttrToFadeWrapperMap); mFadeableUsages = fmc.mFadeableUsages.clone(); @@ -1459,6 +1459,14 @@ public final class FadeManagerConfiguration implements Parcelable { } } + private void copyUsageToFadeWrapperMapInternal( + SparseArray<FadeVolumeShaperConfigsWrapper> usageToFadeWrapperMap) { + for (int index = 0; index < usageToFadeWrapperMap.size(); index++) { + mUsageToFadeWrapperMap.put(usageToFadeWrapperMap.keyAt(index), + new FadeVolumeShaperConfigsWrapper(usageToFadeWrapperMap.valueAt(index))); + } + } + private void validateFadeState(int state) { switch(state) { case FADE_STATE_DISABLED: @@ -1551,6 +1559,12 @@ public final class FadeManagerConfiguration implements Parcelable { FadeVolumeShaperConfigsWrapper() {} + FadeVolumeShaperConfigsWrapper(@NonNull FadeVolumeShaperConfigsWrapper wrapper) { + Objects.requireNonNull(wrapper, "Fade volume shaper configs wrapper cannot be null"); + this.mFadeOutVolShaperConfig = wrapper.mFadeOutVolShaperConfig; + this.mFadeInVolShaperConfig = wrapper.mFadeInVolShaperConfig; + } + public void setFadeOutVolShaperConfig(@Nullable VolumeShaper.Configuration fadeOutConfig) { mFadeOutVolShaperConfig = fadeOutConfig; } diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig index 50fdb8aaf3cd..91c4f118658a 100644 --- a/media/java/android/media/flags/media_better_together.aconfig +++ b/media/java/android/media/flags/media_better_together.aconfig @@ -25,13 +25,6 @@ flag { } flag { - name: "disable_screen_off_broadcast_receiver" - namespace: "media_solutions" - description: "Disables the broadcast receiver that prevents scanning when the screen is off." - bug: "304234628" -} - -flag { name: "fallback_to_default_handling_when_media_session_has_fixed_volume_handling" namespace: "media_solutions" description: "Fallbacks to the default handling for volume adjustment when media session has fixed volume handling and its app is in the foreground and setting a media controller." @@ -108,6 +101,16 @@ flag { } flag { + name: "enable_mr2_service_non_main_bg_thread" + namespace: "media_solutions" + description: "Enables the use of a background thread in the media routing framework, instead of using the main thread." + bug: "310145678" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "enable_screen_off_scanning" is_exported: true namespace: "media_solutions" diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java index c48a95625e8f..74b5afe9b65b 100644 --- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java +++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java @@ -196,8 +196,7 @@ public final class FadeManagerConfigurationUnitTest { FadeManagerConfiguration fmcObj = new FadeManagerConfiguration .Builder(TEST_FADE_OUT_DURATION_MS, TEST_FADE_IN_DURATION_MS).build(); - FadeManagerConfiguration fmc = new FadeManagerConfiguration - .Builder(fmcObj).build(); + FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder(fmcObj).build(); expect.withMessage("Fade state for copy builder").that(fmc.getFadeState()) .isEqualTo(fmcObj.getFadeState()); @@ -249,6 +248,45 @@ public final class FadeManagerConfigurationUnitTest { } @Test + public void build_withCopyConstructor_doesnotChangeOriginal() { + FadeManagerConfiguration copyConstructedFmc = new FadeManagerConfiguration.Builder(mFmc) + .setFadeOutDurationForUsage(AudioAttributes.USAGE_MEDIA, TEST_FADE_OUT_DURATION_MS) + .setFadeInDurationForUsage(AudioAttributes.USAGE_MEDIA, TEST_FADE_IN_DURATION_MS) + .build(); + + expect.withMessage("Fade out duration for media usage of default constructor") + .that(mFmc.getFadeOutDurationForUsage(AudioAttributes.USAGE_MEDIA)) + .isEqualTo(DEFAULT_FADE_OUT_DURATION_MS); + expect.withMessage("Fade out duration for media usage of default constructor") + .that(mFmc.getFadeInDurationForUsage(AudioAttributes.USAGE_MEDIA)) + .isEqualTo(DEFAULT_FADE_IN_DURATION_MS); + expect.withMessage("Fade out duration for media usage of copy constructor") + .that(copyConstructedFmc.getFadeOutDurationForUsage(AudioAttributes.USAGE_MEDIA)) + .isEqualTo(TEST_FADE_OUT_DURATION_MS); + expect.withMessage("Fade out duration for media usage of copy constructor") + .that(copyConstructedFmc.getFadeInDurationForUsage(AudioAttributes.USAGE_MEDIA)) + .isEqualTo(TEST_FADE_IN_DURATION_MS); + } + + @Test + public void build_withCopyConstructor_equals() { + FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder() + .setFadeableUsages(List.of(AudioAttributes.USAGE_MEDIA, + AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE, + AudioAttributes.USAGE_ASSISTANT, + AudioAttributes.USAGE_EMERGENCY)) + .setFadeOutDurationForUsage(AudioAttributes.USAGE_MEDIA, TEST_FADE_OUT_DURATION_MS) + .setFadeInDurationForUsage(AudioAttributes.USAGE_MEDIA, TEST_FADE_IN_DURATION_MS) + .build(); + + FadeManagerConfiguration copyConstructedFmc = + new FadeManagerConfiguration.Builder(fmc).build(); + + expect.withMessage("Fade manager config constructed using copy constructor").that(fmc) + .isEqualTo(copyConstructedFmc); + } + + @Test public void testGetDefaultFadeOutDuration() { expect.withMessage("Default fade out duration") .that(FadeManagerConfiguration.getDefaultFadeOutDurationMillis()) diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp index 9b1330fc048a..6ce83cd7b765 100644 --- a/native/android/surface_control.cpp +++ b/native/android/surface_control.cpp @@ -367,7 +367,7 @@ void ASurfaceTransaction_reparent(ASurfaceTransaction* aSurfaceTransaction, void ASurfaceTransaction_setVisibility(ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl, - int8_t visibility) { + ASurfaceTransactionVisibility visibility) { CHECK_NOT_NULL(aSurfaceTransaction); CHECK_NOT_NULL(aSurfaceControl); @@ -496,7 +496,7 @@ void ASurfaceTransaction_setScale(ASurfaceTransaction* aSurfaceTransaction, void ASurfaceTransaction_setBufferTransparency(ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl, - int8_t transparency) { + ASurfaceTransactionTransparency transparency) { CHECK_NOT_NULL(aSurfaceTransaction); CHECK_NOT_NULL(aSurfaceControl); diff --git a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java index a8d8f9a1a55d..6f20adf74ee2 100644 --- a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java +++ b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java @@ -39,15 +39,15 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; +import android.util.LongArrayQueue; import android.util.Slog; import android.util.Xml; -import android.utils.BackgroundThread; -import android.utils.LongArrayQueue; -import android.utils.XmlUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.BackgroundThread; import com.android.internal.util.IndentingPrintWriter; +import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; @@ -137,17 +137,6 @@ public class PackageWatchdog { static final int DEFAULT_BOOT_LOOP_TRIGGER_COUNT = 5; static final long DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS = TimeUnit.MINUTES.toMillis(10); - // Boot loop at which packageWatchdog starts first mitigation - private static final String BOOT_LOOP_THRESHOLD = - "persist.device_config.configuration.boot_loop_threshold"; - @VisibleForTesting - static final int DEFAULT_BOOT_LOOP_THRESHOLD = 15; - // Once boot_loop_threshold is surpassed next mitigation would be triggered after - // specified number of reboots. - private static final String BOOT_LOOP_MITIGATION_INCREMENT = - "persist.device_config.configuration..boot_loop_mitigation_increment"; - @VisibleForTesting - static final int DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT = 2; // Threshold level at which or above user might experience significant disruption. private static final String MAJOR_USER_IMPACT_LEVEL_THRESHOLD = @@ -253,15 +242,8 @@ public class PackageWatchdog { mConnectivityModuleConnector = connectivityModuleConnector; mSystemClock = clock; mNumberOfNativeCrashPollsRemaining = NUMBER_OF_NATIVE_CRASH_POLLS; - if (Flags.recoverabilityDetection()) { - mBootThreshold = new BootThreshold(DEFAULT_BOOT_LOOP_TRIGGER_COUNT, - DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS, - SystemProperties.getInt(BOOT_LOOP_MITIGATION_INCREMENT, - DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT)); - } else { - mBootThreshold = new BootThreshold(DEFAULT_BOOT_LOOP_TRIGGER_COUNT, - DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS); - } + mBootThreshold = new BootThreshold(DEFAULT_BOOT_LOOP_TRIGGER_COUNT, + DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS); loadFromFile(); sPackageWatchdog = this; @@ -526,10 +508,16 @@ public class PackageWatchdog { /** * Called when the system server boots. If the system server is detected to be in a boot loop, * query each observer and perform the mitigation action with the lowest user impact. + * + * Note: PackageWatchdog considers system_server restart loop as bootloop. Full reboots + * are not counted in bootloop. */ @SuppressWarnings("GuardedBy") public void noteBoot() { synchronized (mLock) { + // if boot count has reached threshold, start mitigation. + // We wait until threshold number of restarts only for the first time. Perform + // mitigations for every restart after that. boolean mitigate = mBootThreshold.incrementAndTest(); if (mitigate) { if (!Flags.recoverabilityDetection()) { @@ -557,17 +545,13 @@ public class PackageWatchdog { } if (currentObserverToNotify != null) { if (Flags.recoverabilityDetection()) { - if (currentObserverImpact < getUserImpactLevelLimit() - || (currentObserverImpact >= getUserImpactLevelLimit() - && mBootThreshold.getCount() >= getBootLoopThreshold())) { - int currentObserverMitigationCount = - currentObserverInternal.getBootMitigationCount() + 1; - currentObserverInternal.setBootMitigationCount( - currentObserverMitigationCount); - saveAllObserversBootMitigationCountToMetadata(METADATA_FILE); - currentObserverToNotify.executeBootLoopMitigation( - currentObserverMitigationCount); - } + int currentObserverMitigationCount = + currentObserverInternal.getBootMitigationCount() + 1; + currentObserverInternal.setBootMitigationCount( + currentObserverMitigationCount); + saveAllObserversBootMitigationCountToMetadata(METADATA_FILE); + currentObserverToNotify.executeBootLoopMitigation( + currentObserverMitigationCount); } else { mBootThreshold.setMitigationCount(mitigationCount); mBootThreshold.saveMitigationCountToMetadata(); @@ -647,11 +631,6 @@ public class PackageWatchdog { DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD); } - private int getBootLoopThreshold() { - return SystemProperties.getInt(BOOT_LOOP_THRESHOLD, - DEFAULT_BOOT_LOOP_THRESHOLD); - } - /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */ @Retention(SOURCE) @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_LEVEL_0, @@ -1827,16 +1806,10 @@ public class PackageWatchdog { private final int mBootTriggerCount; private final long mTriggerWindow; - private final int mBootMitigationIncrement; BootThreshold(int bootTriggerCount, long triggerWindow) { - this(bootTriggerCount, triggerWindow, /*bootMitigationIncrement=*/ 1); - } - - BootThreshold(int bootTriggerCount, long triggerWindow, int bootMitigationIncrement) { this.mBootTriggerCount = bootTriggerCount; this.mTriggerWindow = triggerWindow; - this.mBootMitigationIncrement = bootMitigationIncrement; } public void reset() { @@ -1915,6 +1888,7 @@ public class PackageWatchdog { } else { readMitigationCountFromMetadataIfNecessary(); } + final long now = mSystemClock.uptimeMillis(); if (now - getStart() < 0) { Slog.e(TAG, "Window was less than zero. Resetting start to current time."); @@ -1939,20 +1913,32 @@ public class PackageWatchdog { setCount(count); EventLogTags.writeRescueNote(Process.ROOT_UID, count, window); if (Flags.recoverabilityDetection()) { - boolean mitigate = (count >= mBootTriggerCount) - && (count - mBootTriggerCount) % mBootMitigationIncrement == 0; - return mitigate; + // After a reboot (e.g. by WARM_REBOOT or mainline rollback) we apply + // mitigations without waiting for DEFAULT_BOOT_LOOP_TRIGGER_COUNT. + return (count >= mBootTriggerCount) + || (performedMitigationsDuringWindow() && count > 1); } return count >= mBootTriggerCount; } } @GuardedBy("mLock") + private boolean performedMitigationsDuringWindow() { + for (ObserverInternal observerInternal: mAllObservers.values()) { + if (observerInternal.getBootMitigationCount() > 0) { + return true; + } + } + return false; + } + + @GuardedBy("mLock") private void resetAllObserversBootMitigationCount() { for (int i = 0; i < mAllObservers.size(); i++) { final ObserverInternal observer = mAllObservers.valueAt(i); observer.setBootMitigationCount(0); } + saveAllObserversBootMitigationCountToMetadata(METADATA_FILE); } @GuardedBy("mLock") diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java index f86eb61c365f..271d552fc574 100644 --- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java +++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java @@ -31,6 +31,7 @@ import android.content.pm.VersionedPackage; import android.crashrecovery.flags.Flags; import android.os.Build; import android.os.Environment; +import android.os.FileUtils; import android.os.PowerManager; import android.os.RecoverySystem; import android.os.SystemClock; @@ -43,11 +44,10 @@ import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; import android.util.Slog; -import android.utils.ArrayUtils; -import android.utils.FileUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; import com.android.server.PackageWatchdog.FailureReasons; import com.android.server.PackageWatchdog.PackageHealthObserver; import com.android.server.PackageWatchdog.PackageHealthObserverImpact; diff --git a/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java b/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java deleted file mode 100644 index fa4d6afc03d3..000000000000 --- a/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.utils; - -import android.annotation.NonNull; -import android.annotation.Nullable; - -import java.io.File; -import java.util.List; -import java.util.Objects; - -/** - * Copied over from frameworks/base/core/java/com/android/internal/util/ArrayUtils.java - * - * @hide - */ -public class ArrayUtils { - private ArrayUtils() { /* cannot be instantiated */ } - public static final File[] EMPTY_FILE = new File[0]; - - - /** - * Return first index of {@code value} in {@code array}, or {@code -1} if - * not found. - */ - public static <T> int indexOf(@Nullable T[] array, T value) { - if (array == null) return -1; - for (int i = 0; i < array.length; i++) { - if (Objects.equals(array[i], value)) return i; - } - return -1; - } - - /** @hide */ - public static @NonNull File[] defeatNullable(@Nullable File[] val) { - return (val != null) ? val : EMPTY_FILE; - } - - /** - * Checks if given array is null or has zero elements. - */ - public static boolean isEmpty(@Nullable int[] array) { - return array == null || array.length == 0; - } - - /** - * True if the byte array is null or has length 0. - */ - public static boolean isEmpty(@Nullable byte[] array) { - return array == null || array.length == 0; - } - - /** - * Converts from List of bytes to byte array - * @param list - * @return byte[] - */ - public static byte[] toPrimitive(List<byte[]> list) { - if (list.size() == 0) { - return new byte[0]; - } - int byteLen = list.get(0).length; - byte[] array = new byte[list.size() * byteLen]; - for (int i = 0; i < list.size(); i++) { - for (int j = 0; j < list.get(i).length; j++) { - array[i * byteLen + j] = list.get(i)[j]; - } - } - return array; - } - - /** - * Adds value to given array if not already present, providing set-like - * behavior. - */ - public static @NonNull int[] appendInt(@Nullable int[] cur, int val) { - return appendInt(cur, val, false); - } - - /** - * Adds value to given array. - */ - public static @NonNull int[] appendInt(@Nullable int[] cur, int val, - boolean allowDuplicates) { - if (cur == null) { - return new int[] { val }; - } - final int n = cur.length; - if (!allowDuplicates) { - for (int i = 0; i < n; i++) { - if (cur[i] == val) { - return cur; - } - } - } - int[] ret = new int[n + 1]; - System.arraycopy(cur, 0, ret, 0, n); - ret[n] = val; - return ret; - } -} diff --git a/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java b/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java deleted file mode 100644 index afcf6895fd0d..000000000000 --- a/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.utils; - -import android.annotation.NonNull; -import android.os.Handler; -import android.os.HandlerThread; - -import com.android.internal.annotations.GuardedBy; - -import java.util.concurrent.Executor; - -/** - * Thread for asynchronous event processing. This thread is configured as - * {@link android.os.Process#THREAD_PRIORITY_BACKGROUND}, which means fewer CPU - * resources will be dedicated to it, and it will "have less chance of impacting - * the responsiveness of the user interface." - * <p> - * This thread is best suited for tasks that the user is not actively waiting - * for, or for tasks that the user expects to be executed eventually. - * - * @see com.android.internal.os.BackgroundThread - * - * TODO: b/326916057 depend on modules-utils-backgroundthread instead - * @hide - */ -public final class BackgroundThread extends HandlerThread { - private static final Object sLock = new Object(); - - @GuardedBy("sLock") - private static BackgroundThread sInstance; - @GuardedBy("sLock") - private static Handler sHandler; - @GuardedBy("sLock") - private static HandlerExecutor sHandlerExecutor; - - private BackgroundThread() { - super(BackgroundThread.class.getName(), android.os.Process.THREAD_PRIORITY_BACKGROUND); - } - - @GuardedBy("sLock") - private static void ensureThreadLocked() { - if (sInstance == null) { - sInstance = new BackgroundThread(); - sInstance.start(); - sHandler = new Handler(sInstance.getLooper()); - sHandlerExecutor = new HandlerExecutor(sHandler); - } - } - - /** - * Get the singleton instance of this class. - * - * @return the singleton instance of this class - */ - @NonNull - public static BackgroundThread get() { - synchronized (sLock) { - ensureThreadLocked(); - return sInstance; - } - } - - /** - * Get the singleton {@link Handler} for this class. - * - * @return the singleton {@link Handler} for this class. - */ - @NonNull - public static Handler getHandler() { - synchronized (sLock) { - ensureThreadLocked(); - return sHandler; - } - } - - /** - * Get the singleton {@link Executor} for this class. - * - * @return the singleton {@link Executor} for this class. - */ - @NonNull - public static Executor getExecutor() { - synchronized (sLock) { - ensureThreadLocked(); - return sHandlerExecutor; - } - } -} diff --git a/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java b/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java deleted file mode 100644 index e4923bfc4ecb..000000000000 --- a/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.utils; - -import android.annotation.NonNull; -import android.annotation.Nullable; - -import java.io.BufferedInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * Bits and pieces copied from hidden API of android.os.FileUtils. - * - * @hide - */ -public class FileUtils { - /** - * Read a text file into a String, optionally limiting the length. - * - * @param file to read (will not seek, so things like /proc files are OK) - * @param max length (positive for head, negative of tail, 0 for no limit) - * @param ellipsis to add of the file was truncated (can be null) - * @return the contents of the file, possibly truncated - * @throws IOException if something goes wrong reading the file - * @hide - */ - public static @Nullable String readTextFile(@Nullable File file, @Nullable int max, - @Nullable String ellipsis) throws IOException { - InputStream input = new FileInputStream(file); - // wrapping a BufferedInputStream around it because when reading /proc with unbuffered - // input stream, bytes read not equal to buffer size is not necessarily the correct - // indication for EOF; but it is true for BufferedInputStream due to its implementation. - BufferedInputStream bis = new BufferedInputStream(input); - try { - long size = file.length(); - if (max > 0 || (size > 0 && max == 0)) { // "head" mode: read the first N bytes - if (size > 0 && (max == 0 || size < max)) max = (int) size; - byte[] data = new byte[max + 1]; - int length = bis.read(data); - if (length <= 0) return ""; - if (length <= max) return new String(data, 0, length); - if (ellipsis == null) return new String(data, 0, max); - return new String(data, 0, max) + ellipsis; - } else if (max < 0) { // "tail" mode: keep the last N - int len; - boolean rolled = false; - byte[] last = null; - byte[] data = null; - do { - if (last != null) rolled = true; - byte[] tmp = last; - last = data; - data = tmp; - if (data == null) data = new byte[-max]; - len = bis.read(data); - } while (len == data.length); - - if (last == null && len <= 0) return ""; - if (last == null) return new String(data, 0, len); - if (len > 0) { - rolled = true; - System.arraycopy(last, len, last, 0, last.length - len); - System.arraycopy(data, 0, last, last.length - len, len); - } - if (ellipsis == null || !rolled) return new String(last); - return ellipsis + new String(last); - } else { // "cat" mode: size unknown, read it all in streaming fashion - ByteArrayOutputStream contents = new ByteArrayOutputStream(); - int len; - byte[] data = new byte[1024]; - do { - len = bis.read(data); - if (len > 0) contents.write(data, 0, len); - } while (len == data.length); - return contents.toString(); - } - } finally { - bis.close(); - input.close(); - } - } - - /** - * Perform an fsync on the given FileOutputStream. The stream at this - * point must be flushed but not yet closed. - * - * @hide - */ - public static boolean sync(FileOutputStream stream) { - try { - if (stream != null) { - stream.getFD().sync(); - } - return true; - } catch (IOException e) { - } - return false; - } - - /** - * List the files in the directory or return empty file. - * - * @hide - */ - public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) { - return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles()) - : ArrayUtils.EMPTY_FILE; - } -} diff --git a/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java b/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java deleted file mode 100644 index fdb15e2333d5..000000000000 --- a/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.utils; - -import android.annotation.NonNull; -import android.os.Handler; - -import java.util.Objects; -import java.util.concurrent.Executor; -import java.util.concurrent.RejectedExecutionException; - -/** - * An adapter {@link Executor} that posts all executed tasks onto the given - * {@link Handler}. - * - * TODO: b/326916057 depend on modules-utils-backgroundthread instead - * @hide - */ -public class HandlerExecutor implements Executor { - private final Handler mHandler; - - public HandlerExecutor(@NonNull Handler handler) { - mHandler = Objects.requireNonNull(handler); - } - - @Override - public void execute(Runnable command) { - if (!mHandler.post(command)) { - throw new RejectedExecutionException(mHandler + " is shutting down"); - } - } -} diff --git a/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java b/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java deleted file mode 100644 index 5cdc2536129a..000000000000 --- a/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.utils; - -import libcore.util.EmptyArray; - -import java.util.NoSuchElementException; - -/** - * Copied from frameworks/base/core/java/android/util/LongArrayQueue.java - * - * @hide - */ -public class LongArrayQueue { - - private long[] mValues; - private int mSize; - private int mHead; - private int mTail; - - private long[] newUnpaddedLongArray(int num) { - return new long[num]; - } - /** - * Initializes a queue with the given starting capacity. - * - * @param initialCapacity the capacity. - */ - public LongArrayQueue(int initialCapacity) { - if (initialCapacity == 0) { - mValues = EmptyArray.LONG; - } else { - mValues = newUnpaddedLongArray(initialCapacity); - } - mSize = 0; - mHead = mTail = 0; - } - - /** - * Initializes a queue with default starting capacity. - */ - public LongArrayQueue() { - this(16); - } - - /** @hide */ - public static int growSize(int currentSize) { - return currentSize <= 4 ? 8 : currentSize * 2; - } - - private void grow() { - if (mSize < mValues.length) { - throw new IllegalStateException("Queue not full yet!"); - } - final int newSize = growSize(mSize); - final long[] newArray = newUnpaddedLongArray(newSize); - final int r = mValues.length - mHead; // Number of elements on and to the right of head. - System.arraycopy(mValues, mHead, newArray, 0, r); - System.arraycopy(mValues, 0, newArray, r, mHead); - mValues = newArray; - mHead = 0; - mTail = mSize; - } - - /** - * Returns the number of elements in the queue. - */ - public int size() { - return mSize; - } - - /** - * Removes all elements from this queue. - */ - public void clear() { - mSize = 0; - mHead = mTail = 0; - } - - /** - * Adds a value to the tail of the queue. - * - * @param value the value to be added. - */ - public void addLast(long value) { - if (mSize == mValues.length) { - grow(); - } - mValues[mTail] = value; - mTail = (mTail + 1) % mValues.length; - mSize++; - } - - /** - * Removes an element from the head of the queue. - * - * @return the element at the head of the queue. - * @throws NoSuchElementException if the queue is empty. - */ - public long removeFirst() { - if (mSize == 0) { - throw new NoSuchElementException("Queue is empty!"); - } - final long ret = mValues[mHead]; - mHead = (mHead + 1) % mValues.length; - mSize--; - return ret; - } - - /** - * Returns the element at the given position from the head of the queue, where 0 represents the - * head of the queue. - * - * @param position the position from the head of the queue. - * @return the element found at the given position. - * @throws IndexOutOfBoundsException if {@code position} < {@code 0} or - * {@code position} >= {@link #size()} - */ - public long get(int position) { - if (position < 0 || position >= mSize) { - throw new IndexOutOfBoundsException("Index " + position - + " not valid for a queue of size " + mSize); - } - final int index = (mHead + position) % mValues.length; - return mValues[index]; - } - - /** - * Returns the element at the head of the queue, without removing it. - * - * @return the element at the head of the queue. - * @throws NoSuchElementException if the queue is empty - */ - public long peekFirst() { - if (mSize == 0) { - throw new NoSuchElementException("Queue is empty!"); - } - return mValues[mHead]; - } - - /** - * Returns the element at the tail of the queue. - * - * @return the element at the tail of the queue. - * @throws NoSuchElementException if the queue is empty. - */ - public long peekLast() { - if (mSize == 0) { - throw new NoSuchElementException("Queue is empty!"); - } - final int index = (mTail == 0) ? mValues.length - 1 : mTail - 1; - return mValues[index]; - } - - /** - * {@inheritDoc} - */ - @Override - public String toString() { - if (mSize <= 0) { - return "{}"; - } - - final StringBuilder buffer = new StringBuilder(mSize * 64); - buffer.append('{'); - buffer.append(get(0)); - for (int i = 1; i < mSize; i++) { - buffer.append(", "); - buffer.append(get(i)); - } - buffer.append('}'); - return buffer.toString(); - } -} diff --git a/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java b/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java deleted file mode 100644 index dbbef61f6777..000000000000 --- a/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.utils; - -import android.annotation.NonNull; -import android.system.ErrnoException; -import android.system.Os; - -import com.android.modules.utils.TypedXmlPullParser; - -import libcore.util.XmlObjectFactory; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.BufferedInputStream; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * Copied over partly from frameworks/base/core/java/com/android/internal/util/XmlUtils.java - * - * @hide - */ -public class XmlUtils { - - private static final String STRING_ARRAY_SEPARATOR = ":"; - - /** @hide */ - public static final void beginDocument(XmlPullParser parser, String firstElementName) - throws XmlPullParserException, IOException { - int type; - while ((type = parser.next()) != parser.START_TAG - && type != parser.END_DOCUMENT) { - // Do nothing - } - - if (type != parser.START_TAG) { - throw new XmlPullParserException("No start tag found"); - } - - if (!parser.getName().equals(firstElementName)) { - throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() - + ", expected " + firstElementName); - } - } - - /** @hide */ - public static boolean nextElementWithin(XmlPullParser parser, int outerDepth) - throws IOException, XmlPullParserException { - for (;;) { - int type = parser.next(); - if (type == XmlPullParser.END_DOCUMENT - || (type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth)) { - return false; - } - if (type == XmlPullParser.START_TAG - && parser.getDepth() == outerDepth + 1) { - return true; - } - } - } - - private static XmlPullParser newPullParser() { - try { - XmlPullParser parser = XmlObjectFactory.newXmlPullParser(); - parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true); - parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - return parser; - } catch (XmlPullParserException e) { - throw new AssertionError(); - } - } - - /** @hide */ - public static @NonNull TypedXmlPullParser resolvePullParser(@NonNull InputStream in) - throws IOException { - final byte[] magic = new byte[4]; - if (in instanceof FileInputStream) { - try { - Os.pread(((FileInputStream) in).getFD(), magic, 0, magic.length, 0); - } catch (ErrnoException e) { - throw e.rethrowAsIOException(); - } - } else { - if (!in.markSupported()) { - in = new BufferedInputStream(in); - } - in.mark(8); - in.read(magic); - in.reset(); - } - - final TypedXmlPullParser xml; - xml = (TypedXmlPullParser) newPullParser(); - try { - xml.setInput(in, "UTF_8"); - } catch (XmlPullParserException e) { - throw new IOException(e); - } - return xml; - } -} diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp index 79c810ca2611..bd84b58aa0f4 100644 --- a/packages/PackageInstaller/Android.bp +++ b/packages/PackageInstaller/Android.bp @@ -46,7 +46,6 @@ android_app { sdk_version: "system_current", rename_resources_package: false, static_libs: [ - "xz-java", "androidx.leanback_leanback", "androidx.annotation_annotation", "androidx.fragment_fragment", @@ -79,7 +78,6 @@ android_app { overrides: ["PackageInstaller"], static_libs: [ - "xz-java", "androidx.leanback_leanback", "androidx.fragment_fragment", "androidx.lifecycle_lifecycle-livedata", @@ -112,7 +110,6 @@ android_app { overrides: ["PackageInstaller"], static_libs: [ - "xz-java", "androidx.leanback_leanback", "androidx.annotation_annotation", "androidx.fragment_fragment", diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml index bf69d3ba7603..05f4d6954a00 100644 --- a/packages/PackageInstaller/AndroidManifest.xml +++ b/packages/PackageInstaller/AndroidManifest.xml @@ -146,17 +146,6 @@ android:configChanges="mnc|mnc|touchscreen|navigation|screenLayout|screenSize|smallestScreenSize|orientation|locale|keyboard|keyboardHidden|fontScale|uiMode|layoutDirection|density" android:exported="false" /> - <!-- Wearable Components --> - <service android:name=".wear.WearPackageInstallerService" - android:permission="com.google.android.permission.INSTALL_WEARABLE_PACKAGES" - android:foregroundServiceType="systemExempted" - android:exported="true"/> - - <provider android:name=".wear.WearPackageIconProvider" - android:authorities="com.google.android.packageinstaller.wear.provider" - android:grantUriPermissions="true" - android:exported="false" /> - <receiver android:name="androidx.profileinstaller.ProfileInstallReceiver" tools:node="remove" /> diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java deleted file mode 100644 index 53a460dc18ca..000000000000 --- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2016 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.packageinstaller.wear; - -import android.content.Context; -import android.content.IntentSender; -import android.content.pm.PackageInstaller; -import android.os.Looper; -import android.os.ParcelFileDescriptor; -import android.text.TextUtils; -import android.util.Log; - -import java.io.Closeable; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * Task that installs an APK. This must not be called on the main thread. - * This code is based off the Finsky/Wearsky implementation - */ -public class InstallTask { - private static final String TAG = "InstallTask"; - - private static final int DEFAULT_BUFFER_SIZE = 8192; - - private final Context mContext; - private String mPackageName; - private ParcelFileDescriptor mParcelFileDescriptor; - private PackageInstallerImpl.InstallListener mCallback; - private PackageInstaller.Session mSession; - private IntentSender mCommitCallback; - - private Exception mException = null; - private int mErrorCode = 0; - private String mErrorDesc = null; - - public InstallTask(Context context, String packageName, - ParcelFileDescriptor parcelFileDescriptor, - PackageInstallerImpl.InstallListener callback, PackageInstaller.Session session, - IntentSender commitCallback) { - mContext = context; - mPackageName = packageName; - mParcelFileDescriptor = parcelFileDescriptor; - mCallback = callback; - mSession = session; - mCommitCallback = commitCallback; - } - - public boolean isError() { - return mErrorCode != InstallerConstants.STATUS_SUCCESS || !TextUtils.isEmpty(mErrorDesc); - } - - public void execute() { - if (Looper.myLooper() == Looper.getMainLooper()) { - throw new IllegalStateException("This method cannot be called from the UI thread."); - } - - OutputStream sessionStream = null; - try { - sessionStream = mSession.openWrite(mPackageName, 0, -1); - - // 2b: Stream the asset to the installer. Note: - // Note: writeToOutputStreamFromAsset() always safely closes the input stream - writeToOutputStreamFromAsset(sessionStream); - mSession.fsync(sessionStream); - } catch (Exception e) { - mException = e; - mErrorCode = InstallerConstants.ERROR_INSTALL_COPY_STREAM; - mErrorDesc = "Could not write to stream"; - } finally { - if (sessionStream != null) { - // 2c: close output stream - try { - sessionStream.close(); - } catch (Exception e) { - // Ignore otherwise - if (mException == null) { - mException = e; - mErrorCode = InstallerConstants.ERROR_INSTALL_CLOSE_STREAM; - mErrorDesc = "Could not close session stream"; - } - } - } - } - - if (mErrorCode != InstallerConstants.STATUS_SUCCESS) { - // An error occurred, we're done - Log.e(TAG, "Exception while installing " + mPackageName + ": " + mErrorCode + ", " - + mErrorDesc + ", " + mException); - mSession.close(); - mCallback.installFailed(mErrorCode, "[" + mPackageName + "]" + mErrorDesc); - } else { - // 3. Commit the session (this actually installs it.) Session map - // will be cleaned up in the callback. - mCallback.installBeginning(); - mSession.commit(mCommitCallback); - mSession.close(); - } - } - - /** - * {@code PackageInstaller} works with streams. Get the {@code FileDescriptor} - * corresponding to the {@code Asset} and then write the contents into an - * {@code OutputStream} that is passed in. - * <br> - * The {@code FileDescriptor} is closed but the {@code OutputStream} is not closed. - */ - private boolean writeToOutputStreamFromAsset(OutputStream outputStream) { - if (outputStream == null) { - mErrorCode = InstallerConstants.ERROR_INSTALL_COPY_STREAM_EXCEPTION; - mErrorDesc = "Got a null OutputStream."; - return false; - } - - if (mParcelFileDescriptor == null || mParcelFileDescriptor.getFileDescriptor() == null) { - mErrorCode = InstallerConstants.ERROR_COULD_NOT_GET_FD; - mErrorDesc = "Could not get FD"; - return false; - } - - InputStream inputStream = null; - try { - byte[] inputBuf = new byte[DEFAULT_BUFFER_SIZE]; - int bytesRead; - inputStream = new ParcelFileDescriptor.AutoCloseInputStream(mParcelFileDescriptor); - - while ((bytesRead = inputStream.read(inputBuf)) > -1) { - if (bytesRead > 0) { - outputStream.write(inputBuf, 0, bytesRead); - } - } - - outputStream.flush(); - } catch (IOException e) { - mErrorCode = InstallerConstants.ERROR_INSTALL_APK_COPY_FAILURE; - mErrorDesc = "Reading from Asset FD or writing to temp file failed: " + e; - return false; - } finally { - safeClose(inputStream); - } - - return true; - } - - /** - * Quietly close a closeable resource (e.g. a stream or file). The input may already - * be closed and it may even be null. - */ - public static void safeClose(Closeable resource) { - if (resource != null) { - try { - resource.close(); - } catch (IOException ioe) { - // Catch and discard the error - } - } - } -}
\ No newline at end of file diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java deleted file mode 100644 index 3daf3d831d97..000000000000 --- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2016 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.packageinstaller.wear; - -/** - * Constants for Installation / Uninstallation requests. - * Using the same values as Finsky/Wearsky code for consistency in user analytics of failures - */ -public class InstallerConstants { - /** Request succeeded */ - public static final int STATUS_SUCCESS = 0; - - /** - * The new PackageInstaller also returns a small set of less granular error codes, which - * we'll remap to the range -500 and below to keep away from existing installer codes - * (which run from -1 to -110). - */ - public final static int ERROR_PACKAGEINSTALLER_BASE = -500; - - public static final int ERROR_COULD_NOT_GET_FD = -603; - /** This node is not targeted by this request. */ - - /** The install did not complete because could not create PackageInstaller session */ - public final static int ERROR_INSTALL_CREATE_SESSION = -612; - /** The install did not complete because could not open PackageInstaller session */ - public final static int ERROR_INSTALL_OPEN_SESSION = -613; - /** The install did not complete because could not open PackageInstaller output stream */ - public final static int ERROR_INSTALL_OPEN_STREAM = -614; - /** The install did not complete because of an exception while streaming bytes */ - public final static int ERROR_INSTALL_COPY_STREAM_EXCEPTION = -615; - /** The install did not complete because of an unexpected exception from PackageInstaller */ - public final static int ERROR_INSTALL_SESSION_EXCEPTION = -616; - /** The install did not complete because of an unexpected userActionRequired callback */ - public final static int ERROR_INSTALL_USER_ACTION_REQUIRED = -617; - /** The install did not complete because of an unexpected broadcast (missing fields) */ - public final static int ERROR_INSTALL_MALFORMED_BROADCAST = -618; - /** The install did not complete because of an error while copying from downloaded file */ - public final static int ERROR_INSTALL_APK_COPY_FAILURE = -619; - /** The install did not complete because of an error while copying to the PackageInstaller - * output stream */ - public final static int ERROR_INSTALL_COPY_STREAM = -620; - /** The install did not complete because of an error while closing the PackageInstaller - * output stream */ - public final static int ERROR_INSTALL_CLOSE_STREAM = -621; -}
\ No newline at end of file diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java deleted file mode 100644 index bdc22cf0e276..000000000000 --- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2016 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.packageinstaller.wear; - -import android.content.Context; - -/** - * Factory that creates a Package Installer. - */ -public class PackageInstallerFactory { - private static PackageInstallerImpl sPackageInstaller; - - /** - * Return the PackageInstaller shared object. {@code init} should have already been called. - */ - public synchronized static PackageInstallerImpl getPackageInstaller(Context context) { - if (sPackageInstaller == null) { - sPackageInstaller = new PackageInstallerImpl(context); - } - return sPackageInstaller; - } -}
\ No newline at end of file diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java deleted file mode 100644 index 1e37f15f714d..000000000000 --- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java +++ /dev/null @@ -1,327 +0,0 @@ -/* - * Copyright (C) 2016 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.packageinstaller.wear; - -import android.annotation.TargetApi; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.IntentSender; -import android.content.pm.PackageInstaller; -import android.os.Build; -import android.os.ParcelFileDescriptor; -import android.util.Log; - -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Implementation of package manager installation using modern PackageInstaller api. - * - * Heavily copied from Wearsky/Finsky implementation - */ -@TargetApi(Build.VERSION_CODES.LOLLIPOP) -public class PackageInstallerImpl { - private static final String TAG = "PackageInstallerImpl"; - - /** Intent actions used for broadcasts from PackageInstaller back to the local receiver */ - private static final String ACTION_INSTALL_COMMIT = - "com.android.vending.INTENT_PACKAGE_INSTALL_COMMIT"; - - private final Context mContext; - private final PackageInstaller mPackageInstaller; - private final Map<String, PackageInstaller.SessionInfo> mSessionInfoMap; - private final Map<String, PackageInstaller.Session> mOpenSessionMap; - - public PackageInstallerImpl(Context context) { - mContext = context.getApplicationContext(); - mPackageInstaller = mContext.getPackageManager().getPackageInstaller(); - - // Capture a map of known sessions - // This list will be pruned a bit later (stale sessions will be canceled) - mSessionInfoMap = new HashMap<String, PackageInstaller.SessionInfo>(); - List<PackageInstaller.SessionInfo> mySessions = mPackageInstaller.getMySessions(); - for (int i = 0; i < mySessions.size(); i++) { - PackageInstaller.SessionInfo sessionInfo = mySessions.get(i); - String packageName = sessionInfo.getAppPackageName(); - PackageInstaller.SessionInfo oldInfo = mSessionInfoMap.put(packageName, sessionInfo); - - // Checking for old info is strictly for logging purposes - if (oldInfo != null) { - Log.w(TAG, "Multiple sessions for " + packageName + " found. Removing " + oldInfo - .getSessionId() + " & keeping " + mySessions.get(i).getSessionId()); - } - } - mOpenSessionMap = new HashMap<String, PackageInstaller.Session>(); - } - - /** - * This callback will be made after an installation attempt succeeds or fails. - */ - public interface InstallListener { - /** - * This callback signals that preflight checks have succeeded and installation - * is beginning. - */ - void installBeginning(); - - /** - * This callback signals that installation has completed. - */ - void installSucceeded(); - - /** - * This callback signals that installation has failed. - */ - void installFailed(int errorCode, String errorDesc); - } - - /** - * This is a placeholder implementation that bundles an entire "session" into a single - * call. This will be replaced by more granular versions that allow longer session lifetimes, - * download progress tracking, etc. - * - * This must not be called on main thread. - */ - public void install(final String packageName, ParcelFileDescriptor parcelFileDescriptor, - final InstallListener callback) { - // 0. Generic try/catch block because I am not really sure what exceptions (other than - // IOException) might be thrown by PackageInstaller and I want to handle them - // at least slightly gracefully. - try { - // 1. Create or recover a session, and open it - // Try recovery first - PackageInstaller.Session session = null; - PackageInstaller.SessionInfo sessionInfo = mSessionInfoMap.get(packageName); - if (sessionInfo != null) { - // See if it's openable, or already held open - session = getSession(packageName); - } - // If open failed, or there was no session, create a new one and open it. - // If we cannot create or open here, the failure is terminal. - if (session == null) { - try { - innerCreateSession(packageName); - } catch (IOException ioe) { - Log.e(TAG, "Can't create session for " + packageName + ": " + ioe.getMessage()); - callback.installFailed(InstallerConstants.ERROR_INSTALL_CREATE_SESSION, - "Could not create session"); - mSessionInfoMap.remove(packageName); - return; - } - sessionInfo = mSessionInfoMap.get(packageName); - try { - session = mPackageInstaller.openSession(sessionInfo.getSessionId()); - mOpenSessionMap.put(packageName, session); - } catch (SecurityException se) { - Log.e(TAG, "Can't open session for " + packageName + ": " + se.getMessage()); - callback.installFailed(InstallerConstants.ERROR_INSTALL_OPEN_SESSION, - "Can't open session"); - mSessionInfoMap.remove(packageName); - return; - } - } - - // 2. Launch task to handle file operations. - InstallTask task = new InstallTask( mContext, packageName, parcelFileDescriptor, - callback, session, - getCommitCallback(packageName, sessionInfo.getSessionId(), callback)); - task.execute(); - if (task.isError()) { - cancelSession(sessionInfo.getSessionId(), packageName); - } - } catch (Exception e) { - Log.e(TAG, "Unexpected exception while installing: " + packageName + ": " - + e.getMessage()); - callback.installFailed(InstallerConstants.ERROR_INSTALL_SESSION_EXCEPTION, - "Unexpected exception while installing " + packageName); - } - } - - /** - * Retrieve an existing session. Will open if needed, but does not attempt to create. - */ - private PackageInstaller.Session getSession(String packageName) { - // Check for already-open session - PackageInstaller.Session session = mOpenSessionMap.get(packageName); - if (session != null) { - try { - // Probe the session to ensure that it's still open. This may or may not - // throw (if non-open), but it may serve as a canary for stale sessions. - session.getNames(); - return session; - } catch (IOException ioe) { - Log.e(TAG, "Stale open session for " + packageName + ": " + ioe.getMessage()); - mOpenSessionMap.remove(packageName); - } catch (SecurityException se) { - Log.e(TAG, "Stale open session for " + packageName + ": " + se.getMessage()); - mOpenSessionMap.remove(packageName); - } - } - // Check to see if this is a known session - PackageInstaller.SessionInfo sessionInfo = mSessionInfoMap.get(packageName); - if (sessionInfo == null) { - return null; - } - // Try to open it. If we fail here, assume that the SessionInfo was stale. - try { - session = mPackageInstaller.openSession(sessionInfo.getSessionId()); - } catch (SecurityException se) { - Log.w(TAG, "SessionInfo was stale for " + packageName + " - deleting info"); - mSessionInfoMap.remove(packageName); - return null; - } catch (IOException ioe) { - Log.w(TAG, "IOException opening old session for " + ioe.getMessage() - + " - deleting info"); - mSessionInfoMap.remove(packageName); - return null; - } - mOpenSessionMap.put(packageName, session); - return session; - } - - /** This version throws an IOException when the session cannot be created */ - private void innerCreateSession(String packageName) throws IOException { - if (mSessionInfoMap.containsKey(packageName)) { - Log.w(TAG, "Creating session for " + packageName + " when one already exists"); - return; - } - PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( - PackageInstaller.SessionParams.MODE_FULL_INSTALL); - params.setAppPackageName(packageName); - - // IOException may be thrown at this point - int sessionId = mPackageInstaller.createSession(params); - PackageInstaller.SessionInfo sessionInfo = mPackageInstaller.getSessionInfo(sessionId); - mSessionInfoMap.put(packageName, sessionInfo); - } - - /** - * Cancel a session based on its sessionId. Package name is for logging only. - */ - private void cancelSession(int sessionId, String packageName) { - // Close if currently held open - closeSession(packageName); - // Remove local record - mSessionInfoMap.remove(packageName); - try { - mPackageInstaller.abandonSession(sessionId); - } catch (SecurityException se) { - // The session no longer exists, so we can exit quietly. - return; - } - } - - /** - * Close a session if it happens to be held open. - */ - private void closeSession(String packageName) { - PackageInstaller.Session session = mOpenSessionMap.remove(packageName); - if (session != null) { - // Unfortunately close() is not idempotent. Try our best to make this safe. - try { - session.close(); - } catch (Exception e) { - Log.w(TAG, "Unexpected error closing session for " + packageName + ": " - + e.getMessage()); - } - } - } - - /** - * Creates a commit callback for the package install that's underway. This will be called - * some time after calling session.commit() (above). - */ - private IntentSender getCommitCallback(final String packageName, final int sessionId, - final InstallListener callback) { - // Create a single-use broadcast receiver - BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - mContext.unregisterReceiver(this); - handleCommitCallback(intent, packageName, sessionId, callback); - } - }; - // Create a matching intent-filter and register the receiver - String action = ACTION_INSTALL_COMMIT + "." + packageName; - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(action); - mContext.registerReceiver(broadcastReceiver, intentFilter, - Context.RECEIVER_EXPORTED); - - // Create a matching PendingIntent and use it to generate the IntentSender - Intent broadcastIntent = new Intent(action).setPackage(mContext.getPackageName()); - PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, packageName.hashCode(), - broadcastIntent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT - | PendingIntent.FLAG_MUTABLE); - return pendingIntent.getIntentSender(); - } - - /** - * Examine the extras to determine information about the package update/install, decode - * the result, and call the appropriate callback. - * - * @param intent The intent, which the PackageInstaller will have added Extras to - * @param packageName The package name we created the receiver for - * @param sessionId The session Id we created the receiver for - * @param callback The callback to report success/failure to - */ - private void handleCommitCallback(Intent intent, String packageName, int sessionId, - InstallListener callback) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Installation of " + packageName + " finished with extras " - + intent.getExtras()); - } - String statusMessage = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE); - int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, Integer.MIN_VALUE); - if (status == PackageInstaller.STATUS_SUCCESS) { - cancelSession(sessionId, packageName); - callback.installSucceeded(); - } else if (status == -1 /*PackageInstaller.STATUS_USER_ACTION_REQUIRED*/) { - // TODO - use the constant when the correct/final name is in the SDK - // TODO This is unexpected, so we are treating as failure for now - cancelSession(sessionId, packageName); - callback.installFailed(InstallerConstants.ERROR_INSTALL_USER_ACTION_REQUIRED, - "Unexpected: user action required"); - } else { - cancelSession(sessionId, packageName); - int errorCode = getPackageManagerErrorCode(status); - Log.e(TAG, "Error " + errorCode + " while installing " + packageName + ": " - + statusMessage); - callback.installFailed(errorCode, null); - } - } - - private int getPackageManagerErrorCode(int status) { - // This is a hack: because PackageInstaller now reports error codes - // with small positive values, we need to remap them into a space - // that is more compatible with the existing package manager error codes. - // See https://sites.google.com/a/google.com/universal-store/documentation - // /android-client/download-error-codes - int errorCode; - if (status == Integer.MIN_VALUE) { - errorCode = InstallerConstants.ERROR_INSTALL_MALFORMED_BROADCAST; - } else { - errorCode = InstallerConstants.ERROR_PACKAGEINSTALLER_BASE - status; - } - return errorCode; - } -} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java deleted file mode 100644 index 2c289b2a6f94..000000000000 --- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2015 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.packageinstaller.wear; - -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; - -/** - * Installation Util that contains a list of parameters that are needed for - * installing/uninstalling. - */ -public class WearPackageArgs { - private static final String KEY_PACKAGE_NAME = - "com.google.android.clockwork.EXTRA_PACKAGE_NAME"; - private static final String KEY_ASSET_URI = - "com.google.android.clockwork.EXTRA_ASSET_URI"; - private static final String KEY_START_ID = - "com.google.android.clockwork.EXTRA_START_ID"; - private static final String KEY_PERM_URI = - "com.google.android.clockwork.EXTRA_PERM_URI"; - private static final String KEY_CHECK_PERMS = - "com.google.android.clockwork.EXTRA_CHECK_PERMS"; - private static final String KEY_SKIP_IF_SAME_VERSION = - "com.google.android.clockwork.EXTRA_SKIP_IF_SAME_VERSION"; - private static final String KEY_COMPRESSION_ALG = - "com.google.android.clockwork.EXTRA_KEY_COMPRESSION_ALG"; - private static final String KEY_COMPANION_SDK_VERSION = - "com.google.android.clockwork.EXTRA_KEY_COMPANION_SDK_VERSION"; - private static final String KEY_COMPANION_DEVICE_VERSION = - "com.google.android.clockwork.EXTRA_KEY_COMPANION_DEVICE_VERSION"; - private static final String KEY_SHOULD_CHECK_GMS_DEPENDENCY = - "com.google.android.clockwork.EXTRA_KEY_SHOULD_CHECK_GMS_DEPENDENCY"; - private static final String KEY_SKIP_IF_LOWER_VERSION = - "com.google.android.clockwork.EXTRA_SKIP_IF_LOWER_VERSION"; - - public static String getPackageName(Bundle b) { - return b.getString(KEY_PACKAGE_NAME); - } - - public static Bundle setPackageName(Bundle b, String packageName) { - b.putString(KEY_PACKAGE_NAME, packageName); - return b; - } - - public static Uri getAssetUri(Bundle b) { - return b.getParcelable(KEY_ASSET_URI); - } - - public static Uri getPermUri(Bundle b) { - return b.getParcelable(KEY_PERM_URI); - } - - public static boolean checkPerms(Bundle b) { - return b.getBoolean(KEY_CHECK_PERMS); - } - - public static boolean skipIfSameVersion(Bundle b) { - return b.getBoolean(KEY_SKIP_IF_SAME_VERSION); - } - - public static int getCompanionSdkVersion(Bundle b) { - return b.getInt(KEY_COMPANION_SDK_VERSION); - } - - public static int getCompanionDeviceVersion(Bundle b) { - return b.getInt(KEY_COMPANION_DEVICE_VERSION); - } - - public static String getCompressionAlg(Bundle b) { - return b.getString(KEY_COMPRESSION_ALG); - } - - public static int getStartId(Bundle b) { - return b.getInt(KEY_START_ID); - } - - public static boolean skipIfLowerVersion(Bundle b) { - return b.getBoolean(KEY_SKIP_IF_LOWER_VERSION, false); - } - - public static Bundle setStartId(Bundle b, int startId) { - b.putInt(KEY_START_ID, startId); - return b; - } -} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java deleted file mode 100644 index 02b9d298db0e..000000000000 --- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (C) 2015 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.packageinstaller.wear; - -import android.annotation.TargetApi; -import android.app.ActivityManager; -import android.content.ContentProvider; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.net.Uri; -import android.os.Binder; -import android.os.Build; -import android.os.ParcelFileDescriptor; -import android.util.Log; - -import java.io.File; -import java.io.FileNotFoundException; -import java.util.List; - -import static android.content.pm.PackageManager.PERMISSION_GRANTED; - -public class WearPackageIconProvider extends ContentProvider { - private static final String TAG = "WearPackageIconProvider"; - public static final String AUTHORITY = "com.google.android.packageinstaller.wear.provider"; - - private static final String REQUIRED_PERMISSION = - "com.google.android.permission.INSTALL_WEARABLE_PACKAGES"; - - /** MIME types. */ - public static final String ICON_TYPE = "vnd.android.cursor.item/cw_package_icon"; - - @Override - public boolean onCreate() { - return true; - } - - @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { - throw new UnsupportedOperationException("Query is not supported."); - } - - @Override - public String getType(Uri uri) { - if (uri == null) { - throw new IllegalArgumentException("URI passed in is null."); - } - - if (AUTHORITY.equals(uri.getEncodedAuthority())) { - return ICON_TYPE; - } - return null; - } - - @Override - public Uri insert(Uri uri, ContentValues values) { - throw new UnsupportedOperationException("Insert is not supported."); - } - - @Override - public int delete(Uri uri, String selection, String[] selectionArgs) { - if (uri == null) { - throw new IllegalArgumentException("URI passed in is null."); - } - - enforcePermissions(uri); - - if (ICON_TYPE.equals(getType(uri))) { - final File file = WearPackageUtil.getIconFile( - this.getContext().getApplicationContext(), getPackageNameFromUri(uri)); - if (file != null) { - file.delete(); - } - } - - return 0; - } - - @Override - public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - throw new UnsupportedOperationException("Update is not supported."); - } - - @Override - public ParcelFileDescriptor openFile( - Uri uri, @SuppressWarnings("unused") String mode) throws FileNotFoundException { - if (uri == null) { - throw new IllegalArgumentException("URI passed in is null."); - } - - enforcePermissions(uri); - - if (ICON_TYPE.equals(getType(uri))) { - final File file = WearPackageUtil.getIconFile( - this.getContext().getApplicationContext(), getPackageNameFromUri(uri)); - if (file != null) { - return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); - } - } - return null; - } - - public static Uri getUriForPackage(final String packageName) { - return Uri.parse("content://" + AUTHORITY + "/icons/" + packageName + ".icon"); - } - - private String getPackageNameFromUri(Uri uri) { - if (uri == null) { - return null; - } - List<String> pathSegments = uri.getPathSegments(); - String packageName = pathSegments.get(pathSegments.size() - 1); - - if (packageName.endsWith(".icon")) { - packageName = packageName.substring(0, packageName.lastIndexOf(".")); - } - return packageName; - } - - /** - * Make sure the calling app is either a system app or the same app or has the right permission. - * @throws SecurityException if the caller has insufficient permissions. - */ - @TargetApi(Build.VERSION_CODES.BASE_1_1) - private void enforcePermissions(Uri uri) { - // Redo some of the permission check in {@link ContentProvider}. Just add an extra check to - // allow System process to access this provider. - Context context = getContext(); - final int pid = Binder.getCallingPid(); - final int uid = Binder.getCallingUid(); - final int myUid = android.os.Process.myUid(); - - if (uid == myUid || isSystemApp(context, pid)) { - return; - } - - if (context.checkPermission(REQUIRED_PERMISSION, pid, uid) == PERMISSION_GRANTED) { - return; - } - - // last chance, check against any uri grants - if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION) - == PERMISSION_GRANTED) { - return; - } - - throw new SecurityException("Permission Denial: reading " - + getClass().getName() + " uri " + uri + " from pid=" + pid - + ", uid=" + uid); - } - - /** - * From the pid of the calling process, figure out whether this is a system app or not. We do - * this by checking the application information corresponding to the pid and then checking if - * FLAG_SYSTEM is set. - */ - @TargetApi(Build.VERSION_CODES.CUPCAKE) - private boolean isSystemApp(Context context, int pid) { - // Get the Activity Manager Object - ActivityManager aManager = - (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - // Get the list of running Applications - List<ActivityManager.RunningAppProcessInfo> rapInfoList = - aManager.getRunningAppProcesses(); - for (ActivityManager.RunningAppProcessInfo rapInfo : rapInfoList) { - if (rapInfo.pid == pid) { - try { - PackageInfo pkgInfo = context.getPackageManager().getPackageInfo( - rapInfo.pkgList[0], 0); - if (pkgInfo != null && pkgInfo.applicationInfo != null && - (pkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { - Log.d(TAG, pid + " is a system app."); - return true; - } - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, "Could not find package information.", e); - return false; - } - } - } - return false; - } -} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java deleted file mode 100644 index ae0f4ece1c17..000000000000 --- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java +++ /dev/null @@ -1,621 +0,0 @@ -/* - * Copyright (C) 2015 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.packageinstaller.wear; - -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.FeatureInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageInstaller; -import android.content.pm.PackageManager; -import android.content.pm.VersionedPackage; -import android.database.Cursor; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.os.ParcelFileDescriptor; -import android.os.PowerManager; -import android.os.Process; -import android.util.ArrayMap; -import android.util.Log; -import android.util.Pair; -import androidx.annotation.Nullable; -import com.android.packageinstaller.DeviceUtils; -import com.android.packageinstaller.PackageUtil; -import com.android.packageinstaller.R; -import com.android.packageinstaller.common.EventResultPersister; -import com.android.packageinstaller.common.UninstallEventReceiver; -import java.io.File; -import java.io.FileNotFoundException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Service that will install/uninstall packages. It will check for permissions and features as well. - * - * ----------- - * - * Debugging information: - * - * Install Action example: - * adb shell am startservice -a com.android.packageinstaller.wear.INSTALL_PACKAGE \ - * -d package://com.google.android.gms \ - * --eu com.google.android.clockwork.EXTRA_ASSET_URI content://com.google.android.clockwork.home.provider/host/com.google.android.wearable.app/wearable/com.google.android.gms/apk \ - * --es android.intent.extra.INSTALLER_PACKAGE_NAME com.google.android.gms \ - * --ez com.google.android.clockwork.EXTRA_CHECK_PERMS false \ - * --eu com.google.android.clockwork.EXTRA_PERM_URI content://com.google.android.clockwork.home.provider/host/com.google.android.wearable.app/permissions \ - * com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService - * - * Uninstall Action example: - * adb shell am startservice -a com.android.packageinstaller.wear.UNINSTALL_PACKAGE \ - * -d package://com.google.android.gms \ - * com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService - * - * Retry GMS: - * adb shell am startservice -a com.android.packageinstaller.wear.RETRY_GMS \ - * com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService - */ -public class WearPackageInstallerService extends Service - implements EventResultPersister.EventResultObserver { - private static final String TAG = "WearPkgInstallerService"; - - private static final String WEAR_APPS_CHANNEL = "wear_app_install_uninstall"; - private static final String BROADCAST_ACTION = - "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT"; - - private final int START_INSTALL = 1; - private final int START_UNINSTALL = 2; - - private int mInstallNotificationId = 1; - private final Map<String, Integer> mNotifIdMap = new ArrayMap<>(); - private final Map<Integer, UninstallParams> mServiceIdToParams = new HashMap<>(); - - private class UninstallParams { - public String mPackageName; - public PowerManager.WakeLock mLock; - - UninstallParams(String packageName, PowerManager.WakeLock lock) { - mPackageName = packageName; - mLock = lock; - } - } - - private final class ServiceHandler extends Handler { - public ServiceHandler(Looper looper) { - super(looper); - } - - public void handleMessage(Message msg) { - switch (msg.what) { - case START_INSTALL: - installPackage(msg.getData()); - break; - case START_UNINSTALL: - uninstallPackage(msg.getData()); - break; - } - } - } - private ServiceHandler mServiceHandler; - private NotificationChannel mNotificationChannel; - private static volatile PowerManager.WakeLock lockStatic = null; - - @Override - public IBinder onBind(Intent intent) { - return null; - } - - @Override - public void onCreate() { - super.onCreate(); - HandlerThread thread = new HandlerThread("PackageInstallerThread", - Process.THREAD_PRIORITY_BACKGROUND); - thread.start(); - - mServiceHandler = new ServiceHandler(thread.getLooper()); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - if (!DeviceUtils.isWear(this)) { - Log.w(TAG, "Not running on wearable."); - finishServiceEarly(startId); - return START_NOT_STICKY; - } - - if (intent == null) { - Log.w(TAG, "Got null intent."); - finishServiceEarly(startId); - return START_NOT_STICKY; - } - - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Got install/uninstall request " + intent); - } - - Uri packageUri = intent.getData(); - if (packageUri == null) { - Log.e(TAG, "No package URI in intent"); - finishServiceEarly(startId); - return START_NOT_STICKY; - } - - final String packageName = WearPackageUtil.getSanitizedPackageName(packageUri); - if (packageName == null) { - Log.e(TAG, "Invalid package name in URI (expected package:<pkgName>): " + packageUri); - finishServiceEarly(startId); - return START_NOT_STICKY; - } - - PowerManager.WakeLock lock = getLock(this.getApplicationContext()); - if (!lock.isHeld()) { - lock.acquire(); - } - - Bundle intentBundle = intent.getExtras(); - if (intentBundle == null) { - intentBundle = new Bundle(); - } - WearPackageArgs.setStartId(intentBundle, startId); - WearPackageArgs.setPackageName(intentBundle, packageName); - Message msg; - String notifTitle; - if (Intent.ACTION_INSTALL_PACKAGE.equals(intent.getAction())) { - msg = mServiceHandler.obtainMessage(START_INSTALL); - notifTitle = getString(R.string.installing); - } else if (Intent.ACTION_UNINSTALL_PACKAGE.equals(intent.getAction())) { - msg = mServiceHandler.obtainMessage(START_UNINSTALL); - notifTitle = getString(R.string.uninstalling); - } else { - Log.e(TAG, "Unknown action : " + intent.getAction()); - finishServiceEarly(startId); - return START_NOT_STICKY; - } - Pair<Integer, Notification> notifPair = buildNotification(packageName, notifTitle); - startForeground(notifPair.first, notifPair.second); - msg.setData(intentBundle); - mServiceHandler.sendMessage(msg); - return START_NOT_STICKY; - } - - private void installPackage(Bundle argsBundle) { - int startId = WearPackageArgs.getStartId(argsBundle); - final String packageName = WearPackageArgs.getPackageName(argsBundle); - final Uri assetUri = WearPackageArgs.getAssetUri(argsBundle); - final Uri permUri = WearPackageArgs.getPermUri(argsBundle); - boolean checkPerms = WearPackageArgs.checkPerms(argsBundle); - boolean skipIfSameVersion = WearPackageArgs.skipIfSameVersion(argsBundle); - int companionSdkVersion = WearPackageArgs.getCompanionSdkVersion(argsBundle); - int companionDeviceVersion = WearPackageArgs.getCompanionDeviceVersion(argsBundle); - String compressionAlg = WearPackageArgs.getCompressionAlg(argsBundle); - boolean skipIfLowerVersion = WearPackageArgs.skipIfLowerVersion(argsBundle); - - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Installing package: " + packageName + ", assetUri: " + assetUri + - ",permUri: " + permUri + ", startId: " + startId + ", checkPerms: " + - checkPerms + ", skipIfSameVersion: " + skipIfSameVersion + - ", compressionAlg: " + compressionAlg + ", companionSdkVersion: " + - companionSdkVersion + ", companionDeviceVersion: " + companionDeviceVersion + - ", skipIfLowerVersion: " + skipIfLowerVersion); - } - final PackageManager pm = getPackageManager(); - File tempFile = null; - PowerManager.WakeLock lock = getLock(this.getApplicationContext()); - boolean messageSent = false; - try { - PackageInfo existingPkgInfo = null; - try { - existingPkgInfo = pm.getPackageInfo(packageName, - PackageManager.MATCH_ANY_USER | PackageManager.GET_PERMISSIONS); - if (existingPkgInfo != null) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Replacing package:" + packageName); - } - } - } catch (PackageManager.NameNotFoundException e) { - // Ignore this exception. We could not find the package, will treat as a new - // installation. - } - // TODO(28021618): This was left as a temp file due to the fact that this code is being - // deprecated and that we need the bare minimum to continue working moving forward - // If this code is used as reference, this permission logic might want to be - // reworked to use a stream instead of a file so that we don't need to write a - // file at all. Note that there might be some trickiness with opening a stream - // for multiple users. - ParcelFileDescriptor parcelFd = getContentResolver() - .openFileDescriptor(assetUri, "r"); - tempFile = WearPackageUtil.getFileFromFd(WearPackageInstallerService.this, - parcelFd, packageName, compressionAlg); - if (tempFile == null) { - Log.e(TAG, "Could not create a temp file from FD for " + packageName); - return; - } - PackageInfo pkgInfo = PackageUtil.getPackageInfo(this, tempFile, - PackageManager.GET_PERMISSIONS | PackageManager.GET_CONFIGURATIONS); - if (pkgInfo == null) { - Log.e(TAG, "Could not parse apk information for " + packageName); - return; - } - - if (!pkgInfo.packageName.equals(packageName)) { - Log.e(TAG, "Wearable Package Name has to match what is provided for " + - packageName); - return; - } - - ApplicationInfo appInfo = pkgInfo.applicationInfo; - appInfo.sourceDir = tempFile.getPath(); - appInfo.publicSourceDir = tempFile.getPath(); - getLabelAndUpdateNotification(packageName, - getString(R.string.installing_app, appInfo.loadLabel(pm))); - - List<String> wearablePerms = Arrays.asList(pkgInfo.requestedPermissions); - - // Log if the installed pkg has a higher version number. - if (existingPkgInfo != null) { - long longVersionCode = pkgInfo.getLongVersionCode(); - if (existingPkgInfo.getLongVersionCode() == longVersionCode) { - if (skipIfSameVersion) { - Log.w(TAG, "Version number (" + longVersionCode + - ") of new app is equal to existing app for " + packageName + - "; not installing due to versionCheck"); - return; - } else { - Log.w(TAG, "Version number of new app (" + longVersionCode + - ") is equal to existing app for " + packageName); - } - } else if (existingPkgInfo.getLongVersionCode() > longVersionCode) { - if (skipIfLowerVersion) { - // Starting in Feldspar, we are not going to allow downgrades of any app. - Log.w(TAG, "Version number of new app (" + longVersionCode + - ") is lower than existing app ( " - + existingPkgInfo.getLongVersionCode() + - ") for " + packageName + "; not installing due to versionCheck"); - return; - } else { - Log.w(TAG, "Version number of new app (" + longVersionCode + - ") is lower than existing app ( " - + existingPkgInfo.getLongVersionCode() + ") for " + packageName); - } - } - - // Following the Android Phone model, we should only check for permissions for any - // newly defined perms. - if (existingPkgInfo.requestedPermissions != null) { - for (int i = 0; i < existingPkgInfo.requestedPermissions.length; ++i) { - // If the permission is granted, then we will not ask to request it again. - if ((existingPkgInfo.requestedPermissionsFlags[i] & - PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, existingPkgInfo.requestedPermissions[i] + - " is already granted for " + packageName); - } - wearablePerms.remove(existingPkgInfo.requestedPermissions[i]); - } - } - } - } - - // Check that the wearable has all the features. - boolean hasAllFeatures = true; - for (FeatureInfo feature : pkgInfo.reqFeatures) { - if (feature.name != null && !pm.hasSystemFeature(feature.name) && - (feature.flags & FeatureInfo.FLAG_REQUIRED) != 0) { - Log.e(TAG, "Wearable does not have required feature: " + feature + - " for " + packageName); - hasAllFeatures = false; - } - } - - if (!hasAllFeatures) { - return; - } - - // Check permissions on both the new wearable package and also on the already installed - // wearable package. - // If the app is targeting API level 23, we will also start a service in ClockworkHome - // which will ultimately prompt the user to accept/reject permissions. - if (checkPerms && !checkPermissions(pkgInfo, companionSdkVersion, - companionDeviceVersion, permUri, wearablePerms, tempFile)) { - Log.w(TAG, "Wearable does not have enough permissions."); - return; - } - - // Finally install the package. - ParcelFileDescriptor fd = getContentResolver().openFileDescriptor(assetUri, "r"); - PackageInstallerFactory.getPackageInstaller(this).install(packageName, fd, - new PackageInstallListener(this, lock, startId, packageName)); - - messageSent = true; - Log.i(TAG, "Sent installation request for " + packageName); - } catch (FileNotFoundException e) { - Log.e(TAG, "Could not find the file with URI " + assetUri, e); - } finally { - if (!messageSent) { - // Some error happened. If the message has been sent, we can wait for the observer - // which will finish the service. - if (tempFile != null) { - tempFile.delete(); - } - finishService(lock, startId); - } - } - } - - // TODO: This was left using the old PackageManager API due to the fact that this code is being - // deprecated and that we need the bare minimum to continue working moving forward - // If this code is used as reference, this logic should be reworked to use the new - // PackageInstaller APIs similar to how installPackage was reworked - private void uninstallPackage(Bundle argsBundle) { - int startId = WearPackageArgs.getStartId(argsBundle); - final String packageName = WearPackageArgs.getPackageName(argsBundle); - - PowerManager.WakeLock lock = getLock(this.getApplicationContext()); - - UninstallParams params = new UninstallParams(packageName, lock); - mServiceIdToParams.put(startId, params); - - final PackageManager pm = getPackageManager(); - try { - PackageInfo pkgInfo = pm.getPackageInfo(packageName, 0); - getLabelAndUpdateNotification(packageName, - getString(R.string.uninstalling_app, pkgInfo.applicationInfo.loadLabel(pm))); - - int uninstallId = UninstallEventReceiver.addObserver(this, - EventResultPersister.GENERATE_NEW_ID, this); - - Intent broadcastIntent = new Intent(BROADCAST_ACTION); - broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); - broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, uninstallId); - broadcastIntent.putExtra(EventResultPersister.EXTRA_SERVICE_ID, startId); - broadcastIntent.setPackage(getPackageName()); - - PendingIntent pendingIntent = PendingIntent.getBroadcast(this, uninstallId, - broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT - | PendingIntent.FLAG_MUTABLE); - - // Found package, send uninstall request. - pm.getPackageInstaller().uninstall( - new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST), - PackageManager.DELETE_ALL_USERS, - pendingIntent.getIntentSender()); - - Log.i(TAG, "Sent delete request for " + packageName); - } catch (IllegalArgumentException | PackageManager.NameNotFoundException e) { - // Couldn't find the package, no need to call uninstall. - Log.w(TAG, "Could not find package, not deleting " + packageName, e); - finishService(lock, startId); - } catch (EventResultPersister.OutOfIdsException e) { - Log.e(TAG, "Fails to start uninstall", e); - finishService(lock, startId); - } - } - - @Override - public void onResult(int status, int legacyStatus, @Nullable String message, int serviceId) { - if (mServiceIdToParams.containsKey(serviceId)) { - UninstallParams params = mServiceIdToParams.get(serviceId); - try { - if (status == PackageInstaller.STATUS_SUCCESS) { - Log.i(TAG, "Package " + params.mPackageName + " was uninstalled."); - } else { - Log.e(TAG, "Package uninstall failed " + params.mPackageName - + ", returnCode " + legacyStatus); - } - } finally { - finishService(params.mLock, serviceId); - } - } - } - - private boolean checkPermissions(PackageInfo pkgInfo, int companionSdkVersion, - int companionDeviceVersion, Uri permUri, List<String> wearablePermissions, - File apkFile) { - // Assumption: We are running on Android O. - // If the Phone App is targeting M, all permissions may not have been granted to the phone - // app. If the Wear App is then not targeting M, there may be permissions that are not - // granted on the Phone app (by the user) right now and we cannot just grant it for the Wear - // app. - if (pkgInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) { - // Install the app if Wear App is ready for the new perms model. - return true; - } - - if (!doesWearHaveUngrantedPerms(pkgInfo.packageName, permUri, wearablePermissions)) { - // All permissions requested by the watch are already granted on the phone, no need - // to do anything. - return true; - } - - // Log an error if Wear is targeting < 23 and phone is targeting >= 23. - if (companionSdkVersion == 0 || companionSdkVersion >= Build.VERSION_CODES.M) { - Log.e(TAG, "MNC: Wear app's targetSdkVersion should be at least 23, if " - + "phone app is targeting at least 23, will continue."); - } - - return false; - } - - /** - * Given a {@string packageName} corresponding to a phone app, query the provider for all the - * perms that are granted. - * - * @return true if the Wear App has any perms that have not been granted yet on the phone side. - * @return true if there is any error cases. - */ - private boolean doesWearHaveUngrantedPerms(String packageName, Uri permUri, - List<String> wearablePermissions) { - if (permUri == null) { - Log.e(TAG, "Permission URI is null"); - // Pretend there is an ungranted permission to avoid installing for error cases. - return true; - } - Cursor permCursor = getContentResolver().query(permUri, null, null, null, null); - if (permCursor == null) { - Log.e(TAG, "Could not get the cursor for the permissions"); - // Pretend there is an ungranted permission to avoid installing for error cases. - return true; - } - - Set<String> grantedPerms = new HashSet<>(); - Set<String> ungrantedPerms = new HashSet<>(); - while(permCursor.moveToNext()) { - // Make sure that the MatrixCursor returned by the ContentProvider has 2 columns and - // verify their types. - if (permCursor.getColumnCount() == 2 - && Cursor.FIELD_TYPE_STRING == permCursor.getType(0) - && Cursor.FIELD_TYPE_INTEGER == permCursor.getType(1)) { - String perm = permCursor.getString(0); - Integer granted = permCursor.getInt(1); - if (granted == 1) { - grantedPerms.add(perm); - } else { - ungrantedPerms.add(perm); - } - } - } - permCursor.close(); - - boolean hasUngrantedPerm = false; - for (String wearablePerm : wearablePermissions) { - if (!grantedPerms.contains(wearablePerm)) { - hasUngrantedPerm = true; - if (!ungrantedPerms.contains(wearablePerm)) { - // This is an error condition. This means that the wearable has permissions that - // are not even declared in its host app. This is a developer error. - Log.e(TAG, "Wearable " + packageName + " has a permission \"" + wearablePerm - + "\" that is not defined in the host application's manifest."); - } else { - Log.w(TAG, "Wearable " + packageName + " has a permission \"" + wearablePerm + - "\" that is not granted in the host application."); - } - } - } - return hasUngrantedPerm; - } - - /** Finishes the service after fulfilling obligation to call startForeground. */ - private void finishServiceEarly(int startId) { - Pair<Integer, Notification> notifPair = buildNotification( - getApplicationContext().getPackageName(), ""); - startForeground(notifPair.first, notifPair.second); - finishService(null, startId); - } - - private void finishService(PowerManager.WakeLock lock, int startId) { - if (lock != null && lock.isHeld()) { - lock.release(); - } - stopSelf(startId); - } - - private synchronized PowerManager.WakeLock getLock(Context context) { - if (lockStatic == null) { - PowerManager mgr = - (PowerManager) context.getSystemService(Context.POWER_SERVICE); - lockStatic = mgr.newWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, context.getClass().getSimpleName()); - lockStatic.setReferenceCounted(true); - } - return lockStatic; - } - - private class PackageInstallListener implements PackageInstallerImpl.InstallListener { - private Context mContext; - private PowerManager.WakeLock mWakeLock; - private int mStartId; - private String mApplicationPackageName; - private PackageInstallListener(Context context, PowerManager.WakeLock wakeLock, - int startId, String applicationPackageName) { - mContext = context; - mWakeLock = wakeLock; - mStartId = startId; - mApplicationPackageName = applicationPackageName; - } - - @Override - public void installBeginning() { - Log.i(TAG, "Package " + mApplicationPackageName + " is being installed."); - } - - @Override - public void installSucceeded() { - try { - Log.i(TAG, "Package " + mApplicationPackageName + " was installed."); - - // Delete tempFile from the file system. - File tempFile = WearPackageUtil.getTemporaryFile(mContext, mApplicationPackageName); - if (tempFile != null) { - tempFile.delete(); - } - } finally { - finishService(mWakeLock, mStartId); - } - } - - @Override - public void installFailed(int errorCode, String errorDesc) { - Log.e(TAG, "Package install failed " + mApplicationPackageName - + ", errorCode " + errorCode); - finishService(mWakeLock, mStartId); - } - } - - private synchronized Pair<Integer, Notification> buildNotification(final String packageName, - final String title) { - int notifId; - if (mNotifIdMap.containsKey(packageName)) { - notifId = mNotifIdMap.get(packageName); - } else { - notifId = mInstallNotificationId++; - mNotifIdMap.put(packageName, notifId); - } - - if (mNotificationChannel == null) { - mNotificationChannel = new NotificationChannel(WEAR_APPS_CHANNEL, - getString(R.string.wear_app_channel), NotificationManager.IMPORTANCE_MIN); - NotificationManager notificationManager = getSystemService(NotificationManager.class); - notificationManager.createNotificationChannel(mNotificationChannel); - } - return new Pair<>(notifId, new Notification.Builder(this, WEAR_APPS_CHANNEL) - .setSmallIcon(R.drawable.ic_file_download) - .setContentTitle(title) - .build()); - } - - private void getLabelAndUpdateNotification(String packageName, String title) { - // Update notification since we have a label now. - NotificationManager notificationManager = getSystemService(NotificationManager.class); - Pair<Integer, Notification> notifPair = buildNotification(packageName, title); - notificationManager.notify(notifPair.first, notifPair.second); - } -} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java deleted file mode 100644 index 6a9145db9a06..000000000000 --- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2015 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.packageinstaller.wear; - -import android.content.Context; -import android.net.Uri; -import android.os.ParcelFileDescriptor; -import android.system.ErrnoException; -import android.system.Os; -import android.text.TextUtils; -import android.util.Log; - -import org.tukaani.xz.LZMAInputStream; -import org.tukaani.xz.XZInputStream; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; - -public class WearPackageUtil { - private static final String TAG = "WearablePkgInstaller"; - - private static final String COMPRESSION_LZMA = "lzma"; - private static final String COMPRESSION_XZ = "xz"; - - public static File getTemporaryFile(Context context, String packageName) { - try { - File newFileDir = new File(context.getFilesDir(), "tmp"); - newFileDir.mkdirs(); - Os.chmod(newFileDir.getAbsolutePath(), 0771); - File newFile = new File(newFileDir, packageName + ".apk"); - return newFile; - } catch (ErrnoException e) { - Log.e(TAG, "Failed to open.", e); - return null; - } - } - - public static File getIconFile(final Context context, final String packageName) { - try { - File newFileDir = new File(context.getFilesDir(), "images/icons"); - newFileDir.mkdirs(); - Os.chmod(newFileDir.getAbsolutePath(), 0771); - return new File(newFileDir, packageName + ".icon"); - } catch (ErrnoException e) { - Log.e(TAG, "Failed to open.", e); - return null; - } - } - - /** - * In order to make sure that the Wearable Asset Manager has a reasonable apk that can be used - * by the PackageManager, we will parse it before sending it to the PackageManager. - * Unfortunately, ParsingPackageUtils needs a file to parse. So, we have to temporarily convert - * the fd to a File. - * - * @param context - * @param fd FileDescriptor to convert to File - * @param packageName Name of package, will define the name of the file - * @param compressionAlg Can be null. For ALT mode the APK will be compressed. We will - * decompress it here - */ - public static File getFileFromFd(Context context, ParcelFileDescriptor fd, - String packageName, String compressionAlg) { - File newFile = getTemporaryFile(context, packageName); - if (fd == null || fd.getFileDescriptor() == null) { - return null; - } - InputStream fr = new ParcelFileDescriptor.AutoCloseInputStream(fd); - try { - if (TextUtils.equals(compressionAlg, COMPRESSION_XZ)) { - fr = new XZInputStream(fr); - } else if (TextUtils.equals(compressionAlg, COMPRESSION_LZMA)) { - fr = new LZMAInputStream(fr); - } - } catch (IOException e) { - Log.e(TAG, "Compression was set to " + compressionAlg + ", but could not decode ", e); - return null; - } - - int nRead; - byte[] data = new byte[1024]; - try { - final FileOutputStream fo = new FileOutputStream(newFile); - while ((nRead = fr.read(data, 0, data.length)) != -1) { - fo.write(data, 0, nRead); - } - fo.flush(); - fo.close(); - Os.chmod(newFile.getAbsolutePath(), 0644); - return newFile; - } catch (IOException e) { - Log.e(TAG, "Reading from Asset FD or writing to temp file failed ", e); - return null; - } catch (ErrnoException e) { - Log.e(TAG, "Could not set permissions on file ", e); - return null; - } finally { - try { - fr.close(); - } catch (IOException e) { - Log.e(TAG, "Failed to close the file from FD ", e); - } - } - } - - /** - * @return com.google.com from expected formats like - * Uri: package:com.google.com, package:/com.google.com, package://com.google.com - */ - public static String getSanitizedPackageName(Uri packageUri) { - String packageName = packageUri.getEncodedSchemeSpecificPart(); - if (packageName != null) { - return packageName.replaceAll("^/+", ""); - } - return packageName; - } -} diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index a28cfeb4dc80..68167e1598e0 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -1127,7 +1127,7 @@ final class SettingsState { Slog.i(LOG_TAG, "[PERSIST END]"); } } catch (Throwable t) { - Slog.wtf(LOG_TAG, "Failed to write settings, restoring old file", t); + Slog.e(LOG_TAG, "Failed to write settings, restoring old file", t); if (t instanceof IOException) { if (t.getMessage().contains("Couldn't create directory")) { if (DEBUG) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt index fa0a1c4663b1..a31b53383014 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt @@ -33,7 +33,7 @@ import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel -import com.android.systemui.notifications.ui.composable.NotificationStack +import com.android.systemui.notifications.ui.composable.ConstrainedNotificationStack import com.android.systemui.res.R import com.android.systemui.shade.LargeScreenHeaderHelper import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout @@ -94,7 +94,7 @@ constructor( return } - NotificationStack( + ConstrainedNotificationStack( viewModel = viewModel, modifier = modifier.fillMaxWidth().thenIf(shouldUseSplitNotificationShade) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt index cda43476610e..579e837a62d0 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt @@ -36,12 +36,14 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind @@ -60,7 +62,6 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import androidx.compose.ui.util.lerp import com.android.compose.animation.scene.ElementKey @@ -68,12 +69,12 @@ import com.android.compose.animation.scene.NestedScrollBehavior import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.height import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius -import com.android.systemui.notifications.ui.composable.Notifications.Form import com.android.systemui.notifications.ui.composable.Notifications.TransitionThresholds.EXPANSION_FOR_MAX_CORNER_RADIUS import com.android.systemui.notifications.ui.composable.Notifications.TransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.ui.composable.ShadeHeader +import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import kotlin.math.roundToInt @@ -81,7 +82,8 @@ import kotlin.math.roundToInt object Notifications { object Elements { val NotificationScrim = ElementKey("NotificationScrim") - val NotificationPlaceholder = ElementKey("NotificationPlaceholder") + val NotificationStackPlaceholder = ElementKey("NotificationStackPlaceholder") + val HeadsUpNotificationPlaceholder = ElementKey("HeadsUpNotificationPlaceholder") val ShelfSpace = ElementKey("ShelfSpace") } @@ -91,12 +93,6 @@ object Notifications { const val EXPANSION_FOR_MAX_CORNER_RADIUS = 0.1f const val EXPANSION_FOR_MAX_SCRIM_ALPHA = 0.3f } - - enum class Form { - HunFromTop, - Stack, - HunFromBottom, - } } /** @@ -109,24 +105,48 @@ fun SceneScope.HeadsUpNotificationSpace( modifier: Modifier = Modifier, isPeekFromBottom: Boolean = false, ) { - NotificationPlaceholder( - viewModel = viewModel, - form = if (isPeekFromBottom) Form.HunFromBottom else Form.HunFromTop, - modifier = modifier, - ) + val headsUpHeight = viewModel.headsUpHeight.collectAsState() + + Element( + Notifications.Elements.HeadsUpNotificationPlaceholder, + modifier = + modifier + .height { headsUpHeight.value.roundToInt() } + .fillMaxWidth() + .debugBackground(viewModel, DEBUG_HUN_COLOR) + .onGloballyPositioned { coordinates: LayoutCoordinates -> + val boundsInWindow = coordinates.boundsInWindow() + debugLog(viewModel) { + "HUNS onGloballyPositioned:" + + " size=${coordinates.size}" + + " bounds=$boundsInWindow" + } + viewModel.onHeadsUpTopChanged(boundsInWindow.top) + } + ) { + content {} + } } /** Adds the space where notification stack should appear in the scene. */ @Composable -fun SceneScope.NotificationStack( +fun SceneScope.ConstrainedNotificationStack( viewModel: NotificationsPlaceholderViewModel, modifier: Modifier = Modifier, ) { - NotificationPlaceholder( - viewModel = viewModel, - form = Form.Stack, - modifier = modifier, - ) + Box( + modifier = + modifier.onSizeChanged { viewModel.onConstrainedAvailableSpaceChanged(it.height) } + ) { + NotificationPlaceholder( + viewModel = viewModel, + modifier = Modifier.fillMaxSize(), + ) + HeadsUpNotificationSpace( + viewModel = viewModel, + modifier = Modifier.align(Alignment.TopCenter), + ) + } } /** @@ -169,6 +189,9 @@ fun SceneScope.NotificationScrollingStack( // entire height of the scrim is visible on screen. val scrimOffset = remember { mutableStateOf(0f) } + // set the bounds to null when the scrim disappears + DisposableEffect(Unit) { onDispose { viewModel.onScrimBoundsChanged(null) } } + val minScrimTop = with(density) { ShadeHeader.Dimensions.CollapsedHeight.toPx() } // The minimum offset for the scrim. The scrim is considered fully expanded when it @@ -235,6 +258,22 @@ fun SceneScope.NotificationScrollingStack( .let { scrimRounding.value.toRoundedCornerShape(it) } clip = true } + .onGloballyPositioned { coordinates -> + val boundsInWindow = coordinates.boundsInWindow() + debugLog(viewModel) { + "SCRIM onGloballyPositioned:" + + " size=${coordinates.size}" + + " bounds=$boundsInWindow" + } + viewModel.onScrimBoundsChanged( + ShadeScrimBounds( + left = boundsInWindow.left, + top = boundsInWindow.top, + right = boundsInWindow.right, + bottom = boundsInWindow.bottom, + ) + ) + } ) { // Creates a cutout in the background scrim in the shape of the notifications scrim. // Only visible when notif scrim alpha < 1, during shade expansion. @@ -254,11 +293,10 @@ fun SceneScope.NotificationScrollingStack( } else 1f } .background(MaterialTheme.colorScheme.surface) - .debugBackground(viewModel, Color(0.5f, 0.5f, 0f, 0.2f)) + .debugBackground(viewModel, DEBUG_BOX_COLOR) ) { NotificationPlaceholder( viewModel = viewModel, - form = Form.Stack, modifier = Modifier.verticalNestedScrollToScene( topBehavior = NestedScrollBehavior.EdgeWithPreview, @@ -284,6 +322,7 @@ fun SceneScope.NotificationScrollingStack( .height { (stackHeight.value + navBarHeight).roundToInt() }, ) } + HeadsUpNotificationSpace(viewModel = viewModel) } } @@ -304,14 +343,10 @@ fun SceneScope.NotificationShelfSpace( modifier .element(key = Notifications.Elements.ShelfSpace) .fillMaxWidth() - .onSizeChanged { size: IntSize -> - debugLog(viewModel) { "SHELF onSizeChanged: size=$size" } - } .onPlaced { coordinates: LayoutCoordinates -> debugLog(viewModel) { ("SHELF onPlaced:" + " size=${coordinates.size}" + - " position=${coordinates.positionInWindow()}" + " bounds=${coordinates.boundsInWindow()}") } } @@ -326,32 +361,26 @@ fun SceneScope.NotificationShelfSpace( @Composable private fun SceneScope.NotificationPlaceholder( viewModel: NotificationsPlaceholderViewModel, - form: Form, modifier: Modifier = Modifier, ) { - val elementKey = Notifications.Elements.NotificationPlaceholder Element( - elementKey, + Notifications.Elements.NotificationStackPlaceholder, modifier = modifier - .debugBackground(viewModel) - .onSizeChanged { size: IntSize -> - debugLog(viewModel) { "STACK onSizeChanged: size=$size" } - } + .debugBackground(viewModel, DEBUG_STACK_COLOR) + .onSizeChanged { size -> debugLog(viewModel) { "STACK onSizeChanged: size=$size" } } .onGloballyPositioned { coordinates: LayoutCoordinates -> - viewModel.onContentTopChanged(coordinates.positionInWindow().y) + val positionInWindow = coordinates.positionInWindow() debugLog(viewModel) { "STACK onGloballyPositioned:" + " size=${coordinates.size}" + - " position=${coordinates.positionInWindow()}" + + " position=$positionInWindow" + " bounds=${coordinates.boundsInWindow()}" } - val boundsInWindow = coordinates.boundsInWindow() - viewModel.onBoundsChanged( - left = boundsInWindow.left, - top = boundsInWindow.top, - right = boundsInWindow.right, - bottom = boundsInWindow.bottom, + // NOTE: positionInWindow.y scrolls off screen, but boundsInWindow.top will not + viewModel.onStackBoundsChanged( + top = positionInWindow.y, + bottom = positionInWindow.y + coordinates.size.height, ) } ) { @@ -388,7 +417,7 @@ private inline fun debugLog( private fun Modifier.debugBackground( viewModel: NotificationsPlaceholderViewModel, - color: Color = DEBUG_COLOR, + color: Color, ): Modifier = if (viewModel.isVisualDebuggingEnabled) { background(color) @@ -397,8 +426,8 @@ private fun Modifier.debugBackground( } fun ShadeScrimRounding.toRoundedCornerShape(radius: Dp): RoundedCornerShape { - val topRadius = if (roundTop) radius else 0.dp - val bottomRadius = if (roundBottom) radius else 0.dp + val topRadius = if (isTopRounded) radius else 0.dp + val bottomRadius = if (isBottomRounded) radius else 0.dp return RoundedCornerShape( topStart = topRadius, topEnd = topRadius, @@ -408,4 +437,6 @@ fun ShadeScrimRounding.toRoundedCornerShape(radius: Dp): RoundedCornerShape { } private const val TAG = "FlexiNotifs" -private val DEBUG_COLOR = Color(1f, 0f, 0f, 0.2f) +private val DEBUG_STACK_COLOR = Color(1f, 0f, 0f, 0.2f) +private val DEBUG_HUN_COLOR = Color(0f, 0f, 1f, 0.2f) +private val DEBUG_BOX_COLOR = Color(0f, 1f, 0f, 0.2f) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt index 5b9213a8f23c..244f48019dc5 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.ui.composable import android.view.ViewGroup +import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.tween import androidx.compose.animation.expandVertically @@ -122,6 +123,13 @@ private fun SceneScope.QuickSettingsScene( // TODO(b/280887232): implement the real UI. Box(modifier = modifier.fillMaxSize()) { val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState() + + BackHandler( + enabled = isCustomizing, + ) { + viewModel.qsSceneAdapter.requestCloseCustomizer() + } + val collapsedHeaderHeight = with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() } val lifecycleOwner = LocalLifecycleOwner.current diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt index 27c4ec125b59..1a9ad3c285e6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt @@ -496,4 +496,16 @@ class QSSceneAdapterImplTest : SysuiTestCase() { runCurrent() verify(qsImpl!!).setInSplitShade(true) } + + @Test + fun requestCloseCustomizer() = + testScope.runTest { + val qsImpl by collectLastValue(underTest.qsImpl) + + underTest.inflate(context) + runCurrent() + + underTest.requestCloseCustomizer() + verify(qsImpl!!).closeCustomizer() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index ef385673c950..d9ab3b172d2a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -133,18 +133,13 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { } @Test - fun destinationsCustomizing() = + fun destinationsCustomizing_noDestinations() = testScope.runTest { overrideResource(R.bool.config_use_split_notification_shade, false) val destinations by collectLastValue(underTest.destinationScenes) qsFlexiglassAdapter.setCustomizing(true) - assertThat(destinations) - .isEqualTo( - mapOf( - Back to UserActionResult(Scenes.QuickSettings), - ) - ) + assertThat(destinations).isEmpty() } @Test @@ -164,18 +159,13 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { } @Test - fun destinations_whenCustomizing_inSplitShade() = + fun destinations_whenCustomizing_inSplitShade_noDestinations() = testScope.runTest { overrideResource(R.bool.config_use_split_notification_shade, true) val destinations by collectLastValue(underTest.destinationScenes) qsFlexiglassAdapter.setCustomizing(true) - assertThat(destinations) - .isEqualTo( - mapOf( - Back to UserActionResult(Scenes.QuickSettings), - ) - ) + assertThat(destinations).isEmpty() } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt index 53522e276112..3c28c0e4d709 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt @@ -31,7 +31,9 @@ import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds -import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationStackAppearanceViewModel +import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape +import com.android.systemui.statusbar.notification.stack.shared.model.ViewPosition +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationScrollViewModel import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat @@ -57,27 +59,44 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { } private val testScope = kosmos.testScope private val placeholderViewModel by lazy { kosmos.notificationsPlaceholderViewModel } - private val appearanceViewModel by lazy { kosmos.notificationStackAppearanceViewModel } + private val appearanceViewModel by lazy { kosmos.notificationScrollViewModel } private val sceneInteractor by lazy { kosmos.sceneInteractor } private val fakeSceneDataSource by lazy { kosmos.fakeSceneDataSource } @Test fun updateBounds() = testScope.runTest { - val clipping by collectLastValue(appearanceViewModel.shadeScrimClipping) - - val top = 200f - val left = 0f - val bottom = 550f - val right = 100f - placeholderViewModel.onBoundsChanged( - left = left, - top = top, - right = right, - bottom = bottom + val radius = MutableStateFlow(32) + val viewPosition = MutableStateFlow(ViewPosition(0, 0)) + val shape by collectLastValue(appearanceViewModel.shadeScrimShape(radius, viewPosition)) + + placeholderViewModel.onScrimBoundsChanged( + ShadeScrimBounds(left = 0f, top = 200f, right = 100f, bottom = 550f) ) - assertThat(clipping?.bounds) - .isEqualTo(ShadeScrimBounds(left = left, top = top, right = right, bottom = bottom)) + assertThat(shape) + .isEqualTo( + ShadeScrimShape( + bounds = + ShadeScrimBounds(left = 0f, top = 200f, right = 100f, bottom = 550f), + topRadius = 32, + bottomRadius = 0 + ) + ) + + viewPosition.value = ViewPosition(200, 15) + radius.value = 24 + placeholderViewModel.onScrimBoundsChanged( + ShadeScrimBounds(left = 210f, top = 200f, right = 300f, bottom = 550f) + ) + assertThat(shape) + .isEqualTo( + ShadeScrimShape( + bounds = + ShadeScrimBounds(left = 10f, top = 185f, right = 100f, bottom = 535f), + topRadius = 24, + bottomRadius = 0 + ) + ) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt index dc928c49fbd3..50b77dcf9468 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt @@ -68,11 +68,11 @@ class NotificationStackAppearanceInteractorTest : SysuiTestCase() { kosmos.shadeRepository.setShadeMode(ShadeMode.Single) assertThat(stackRounding) - .isEqualTo(ShadeScrimRounding(roundTop = true, roundBottom = false)) + .isEqualTo(ShadeScrimRounding(isTopRounded = true, isBottomRounded = false)) kosmos.shadeRepository.setShadeMode(ShadeMode.Split) assertThat(stackRounding) - .isEqualTo(ShadeScrimRounding(roundTop = true, roundBottom = true)) + .isEqualTo(ShadeScrimRounding(isTopRounded = true, isBottomRounded = true)) } @Test(expected = IllegalStateException::class) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt index d4a704919a49..1f0812da9fe9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt @@ -16,14 +16,10 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel -import android.platform.test.annotations.DisableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags import com.android.systemui.SysuiTestCase -import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds @@ -40,23 +36,21 @@ class NotificationsPlaceholderViewModelTest : SysuiTestCase() { private val underTest = kosmos.notificationsPlaceholderViewModel @Test - @DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun onBoundsChanged_setsNotificationContainerBounds() = + fun onBoundsChanged() = kosmos.testScope.runTest { - underTest.onBoundsChanged(left = 5f, top = 5f, right = 5f, bottom = 5f) - val containerBounds by - collectLastValue(kosmos.keyguardInteractor.notificationContainerBounds) + val bounds = ShadeScrimBounds(left = 5f, top = 15f, right = 25f, bottom = 35f) + underTest.onScrimBoundsChanged(bounds) val stackBounds by collectLastValue(kosmos.notificationStackAppearanceInteractor.shadeScrimBounds) - assertThat(containerBounds) - .isEqualTo(NotificationContainerBounds(top = 5f, bottom = 5f)) - assertThat(stackBounds) - .isEqualTo(ShadeScrimBounds(left = 5f, top = 5f, right = 5f, bottom = 5f)) + assertThat(stackBounds).isEqualTo(bounds) } @Test - fun onContentTopChanged_setsContentTop() { - underTest.onContentTopChanged(padding = 5f) - assertThat(kosmos.notificationStackAppearanceInteractor.stackTop.value).isEqualTo(5f) - } + fun onStackBoundsChanged() = + kosmos.testScope.runTest { + underTest.onStackBoundsChanged(top = 5f, bottom = 500f) + assertThat(kosmos.notificationStackAppearanceInteractor.stackTop.value).isEqualTo(5f) + assertThat(kosmos.notificationStackAppearanceInteractor.stackBottom.value) + .isEqualTo(500f) + } } diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml index 173d57b335f0..3b6b5a0db393 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml @@ -91,6 +91,7 @@ android:layout_height="wrap_content" android:contentDescription="@string/keyguard_accessibility_password" android:gravity="center_horizontal" + android:layout_gravity="center" android:imeOptions="flagForceAscii|actionDone" android:inputType="textPassword" android:maxLength="500" diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml index 909d4fca5018..5aac65396532 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml @@ -55,6 +55,7 @@ android:layout_height="wrap_content" android:contentDescription="@string/keyguard_accessibility_password" android:gravity="center" + android:layout_gravity="center" android:singleLine="true" android:textStyle="normal" android:inputType="textPassword" diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 9d79e8713625..b25c3daa9407 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -403,6 +403,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { if (DeviceEntryUdfpsRefactor.isEnabled()) { if (mOverlay != null && mOverlay.getRequestReason() == REASON_AUTH_KEYGUARD) { mOverlay.updateOverlayParams(mOverlayParams); + } else { + redrawOverlay(); } } else { final boolean wasShowingAlternateBouncer = diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt index ce798ba3912f..7dd883b89db7 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt @@ -17,6 +17,7 @@ package com.android.systemui.common.ui.view import android.view.View +import kotlinx.coroutines.DisposableHandle /** * Set this view's [View#importantForAccessibility] to [View#IMPORTANT_FOR_ACCESSIBILITY_YES] or @@ -43,3 +44,10 @@ inline fun <reified T : View> View.getNearestParent(): T? { } return null } + +/** Adds a [View.OnLayoutChangeListener] and provides a [DisposableHandle] for teardown. */ +fun View.onLayoutChanged(onLayoutChanged: (v: View) -> Unit): DisposableHandle { + val listener = View.OnLayoutChangeListener { v, _, _, _, _, _, _, _, _ -> onLayoutChanged(v) } + addOnLayoutChangeListener(listener) + return DisposableHandle { removeOnLayoutChangeListener(listener) } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt index 68ea5d047e1b..141cca329419 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt @@ -21,6 +21,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.log.core.LogLevel.VERBOSE import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel import javax.inject.Inject @@ -69,9 +70,11 @@ constructor( } } - scope.launch { - sharedNotificationContainerViewModel.bounds.collect { - logger.log(TAG, VERBOSE, "Notif: bounds", it) + if (!SceneContainerFlag.isEnabled) { + scope.launch { + sharedNotificationContainerViewModel.bounds.collect { + logger.log(TAG, VERBOSE, "Notif: bounds", it) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt index 1382468ce7d3..486320af45a2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt @@ -34,6 +34,7 @@ import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.app.tracing.coroutines.launch +import com.android.internal.policy.SystemBarUtils import com.android.systemui.customization.R as customizationR import com.android.systemui.keyguard.shared.model.SettingsClockSize import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRenderer @@ -116,7 +117,7 @@ object KeyguardPreviewClockViewBinder { constrainWidth(R.id.lockscreen_clock_view_large, ConstraintSet.WRAP_CONTENT) constrainHeight(R.id.lockscreen_clock_view_large, ConstraintSet.MATCH_CONSTRAINT) val largeClockTopMargin = - context.resources.getDimensionPixelSize(R.dimen.status_bar_height) + + SystemBarUtils.getStatusBarHeight(context) + context.resources.getDimensionPixelSize( customizationR.dimen.small_clock_padding_top ) + diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index 39df4c3182a2..42bd4aff1dc4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -47,6 +47,7 @@ import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import androidx.core.view.isInvisible +import com.android.internal.policy.SystemBarUtils import com.android.keyguard.ClockEventController import com.android.keyguard.KeyguardClockSwitch import com.android.systemui.animation.view.LaunchableImageView @@ -539,7 +540,7 @@ constructor( ) ) layoutParams.topMargin = - KeyguardPreviewSmartspaceViewModel.getStatusBarHeight(resources) + + SystemBarUtils.getStatusBarHeight(previewContext) + resources.getDimensionPixelSize( com.android.systemui.customization.R.dimen.small_clock_padding_top ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt index f6f3bb1431d6..c9251c7c5473 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.ui.viewmodel import android.content.Context import androidx.annotation.VisibleForTesting import androidx.constraintlayout.helper.widget.Layer +import com.android.internal.policy.SystemBarUtils import com.android.keyguard.KeyguardClockSwitch.LARGE import com.android.keyguard.KeyguardClockSwitch.SMALL import com.android.systemui.customization.R as customizationR @@ -165,7 +166,7 @@ constructor( companion object { fun getLargeClockTopMargin(context: Context): Int { - return context.resources.getDimensionPixelSize(R.dimen.status_bar_height) + + return SystemBarUtils.getStatusBarHeight(context) + context.resources.getDimensionPixelSize( customizationR.dimen.small_clock_padding_top ) + diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt index a90634e24580..b57e3ecbe05b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt @@ -17,7 +17,6 @@ package com.android.systemui.keyguard.ui.viewmodel import android.content.Context -import android.content.res.Resources import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.shared.model.SettingsClockSize @@ -89,14 +88,4 @@ constructor( } } } - companion object { - fun getStatusBarHeight(resource: Resources): Int { - var result = 0 - val resourceId: Int = resource.getIdentifier("status_bar_height", "dimen", "android") - if (resourceId > 0) { - result = resource.getDimensionPixelSize(resourceId) - } - return result - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt index 671050477042..8d5aeab54343 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt @@ -93,6 +93,9 @@ interface QSSceneAdapter { val isQsFullyCollapsed: Boolean get() = true + /** Request that the customizer be closed. Possibly animating it. */ + fun requestCloseCustomizer() + sealed interface State { val isVisible: Boolean @@ -277,6 +280,10 @@ constructor( bottomNavBarSize.emit(padding) } + override fun requestCloseCustomizer() { + qsImpl.value?.closeCustomizer() + } + private fun QSImpl.applyState(state: QSSceneAdapter.State) { setQsVisible(state.isVisible) setExpanded(state.isVisible && state.expansion > 0f) diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt index c695d4c98308..62ed49150eec 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt @@ -20,7 +20,6 @@ import androidx.lifecycle.LifecycleOwner import com.android.compose.animation.scene.Back import com.android.compose.animation.scene.Swipe import com.android.compose.animation.scene.SwipeDirection -import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.FooterActionsController @@ -47,7 +46,9 @@ constructor( val destinationScenes = qsSceneAdapter.isCustomizing.map { customizing -> if (customizing) { - mapOf<UserAction, UserActionResult>(Back to UserActionResult(Scenes.QuickSettings)) + // TODO(b/332749288) Empty map so there are no back handlers and back can close + // customizer + emptyMap() // TODO(b/330200163) Add an Up from Bottom to be able to collapse the shade // while customizing } else { diff --git a/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java b/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java index c42fdf8e7b93..b24edd9beece 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java @@ -25,6 +25,7 @@ import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; import com.android.keyguard.LockIconViewController; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import java.util.HashSet; @@ -80,12 +81,14 @@ public class DebugDrawable extends Drawable { mNotificationPanelViewController.getClockPositionResult() .stackScrollerPadding), Color.YELLOW, "calculatePanelHeightShade()"); - drawDebugInfo(canvas, - (int) mQsController.calculateNotificationsTopPadding( - mNotificationPanelViewController.isExpandingOrCollapsing(), - mNotificationPanelViewController.getKeyguardNotificationStaticPadding(), - mNotificationPanelViewController.getExpandedFraction()), - Color.MAGENTA, "calculateNotificationsTopPadding()"); + if (!SceneContainerFlag.isEnabled()) { + drawDebugInfo(canvas, + (int) mQsController.calculateNotificationsTopPadding( + mNotificationPanelViewController.isExpandingOrCollapsing(), + mNotificationPanelViewController.getKeyguardNotificationStaticPadding(), + mNotificationPanelViewController.getExpandedFraction()), + Color.MAGENTA, "calculateNotificationsTopPadding()"); + } drawDebugInfo(canvas, mNotificationPanelViewController.getClockPositionResult().clockY, Color.GRAY, "mClockPositionResult.clockY"); drawDebugInfo(canvas, (int) mLockIconViewController.getTop(), Color.GRAY, diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index dcfd47b7b303..4660831b77af 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -1588,7 +1588,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump * @param forceClockUpdate Should the clock be updated even when not on keyguard */ private void positionClockAndNotifications(boolean forceClockUpdate) { - boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending(); + boolean animate = !SceneContainerFlag.isEnabled() + && mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending(); int stackScrollerPadding; boolean onKeyguard = isKeyguardShowing(); @@ -1675,7 +1676,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mClockPositionResult.clockX, mClockPositionResult.clockY); } - boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending(); + boolean animate = !SceneContainerFlag.isEnabled() + && mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending(); boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange; if (!MigrateClocksToBlueprint.isEnabled()) { @@ -2483,6 +2485,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump /** Returns the topPadding of notifications when on keyguard not respecting QS expansion. */ int getKeyguardNotificationStaticPadding() { + SceneContainerFlag.assertInLegacyMode(); if (!isKeyguardShowing()) { return 0; } @@ -2524,12 +2527,14 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } void requestScrollerTopPaddingUpdate(boolean animate) { - float padding = mQsController.calculateNotificationsTopPadding(mIsExpandingOrCollapsing, - getKeyguardNotificationStaticPadding(), mExpandedFraction); - if (MigrateClocksToBlueprint.isEnabled()) { - mSharedNotificationContainerInteractor.setTopPosition(padding); - } else { - mNotificationStackScrollLayoutController.updateTopPadding(padding, animate); + if (!SceneContainerFlag.isEnabled()) { + float padding = mQsController.calculateNotificationsTopPadding(mIsExpandingOrCollapsing, + getKeyguardNotificationStaticPadding(), mExpandedFraction); + if (MigrateClocksToBlueprint.isEnabled()) { + mSharedNotificationContainerInteractor.setTopPosition(padding); + } else { + mNotificationStackScrollLayoutController.updateTopPadding(padding, animate); + } } if (isKeyguardShowing() @@ -3964,7 +3969,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mShadeRepository.setLegacyShadeExpansion(mExpandedFraction); mQsController.setShadeExpansion(mExpandedHeight, mExpandedFraction); mExpansionDragDownAmountPx = h; - mAmbientState.setExpansionFraction(mExpandedFraction); + if (!SceneContainerFlag.isEnabled()) { + mAmbientState.setExpansionFraction(mExpandedFraction); + } onHeightUpdated(mExpandedHeight); updateExpansionAndVisibility(); }); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java index 89e84133c783..fb32b9fce909 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java @@ -417,6 +417,12 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW } else { mLpChanged.flags &= ~LayoutParams.FLAG_SECURE; } + + if (state.bouncerShowing) { + mLpChanged.inputFeatures |= LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_TRACING; + } else { + mLpChanged.inputFeatures &= ~LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_TRACING; + } } protected boolean isDebuggable() { diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java index 35b40592a47e..2507507ce22e 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java @@ -1371,6 +1371,7 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum @Override public float calculateNotificationsTopPadding(boolean isShadeExpanding, int keyguardNotificationStaticPadding, float expandedFraction) { + SceneContainerFlag.assertInLegacyMode(); float topPadding; boolean keyguardShowing = mBarState == KEYGUARD; if (mSplitShadeEnabled) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt index b8250cc284bd..34629934e467 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt @@ -17,7 +17,6 @@ package com.android.systemui.shade import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.plugins.qs.QSContainerController import com.android.systemui.qs.ui.adapter.QSSceneAdapter import com.android.systemui.shade.domain.interactor.ShadeInteractor import javax.inject.Inject @@ -28,7 +27,6 @@ class QuickSettingsControllerSceneImpl constructor( private val shadeInteractor: ShadeInteractor, private val qsSceneAdapter: QSSceneAdapter, - private val qsContainerController: QSContainerController, ) : QuickSettingsController { override val expanded: Boolean @@ -43,7 +41,7 @@ constructor( } override fun closeQsCustomizer() { - qsContainerController.setCustomizerShowing(false) + qsSceneAdapter.requestCloseCustomizer() } @Deprecated("specific to legacy split shade") diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt index 2cb9f9acca26..29cdf0a1cb43 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt @@ -43,6 +43,7 @@ import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.NotificationInsetsController import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout +import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import com.android.systemui.statusbar.phone.KeyguardBottomAreaView import com.android.systemui.statusbar.phone.StatusBarLocation @@ -51,6 +52,7 @@ import com.android.systemui.statusbar.phone.TapAgainView import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.tuner.TunerService +import dagger.Binds import dagger.Module import dagger.Provides import javax.inject.Named @@ -59,6 +61,14 @@ import javax.inject.Provider /** Module for providing views related to the shade. */ @Module abstract class ShadeViewProviderModule { + + @Binds + @SysUISingleton + // TODO(b/277762009): Only allow this view's binder to inject the view. + abstract fun bindsNotificationScrollView( + notificationStackScrollLayout: NotificationStackScrollLayout + ): NotificationScrollView + companion object { const val SHADE_HEADER = "large_screen_shade_header" diff --git a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java index 3349345b1391..c4293291de51 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java @@ -345,9 +345,7 @@ public class ShadeCarrierGroupController { } } - if (mStatusBarPipelineFlags.useNewShadeCarrierGroupMobileIcons()) { - Log.d(TAG, "ignoring old pipeline callback because new mobile icon is enabled"); - } else { + if (!mStatusBarPipelineFlags.useNewShadeCarrierGroupMobileIcons()) { for (int i = 0; i < SIM_SLOTS; i++) { mCarrierGroups[i].updateState(mInfos[i], singleCarrier); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index 5b9eb21bf5e9..0bb871b23d27 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -742,7 +742,7 @@ public class AmbientState implements Dumpable { pw.println("mHideSensitive=" + mHideSensitive); pw.println("mShadeExpanded=" + mShadeExpanded); pw.println("mClearAllInProgress=" + mClearAllInProgress); - pw.println("mStatusBarState=" + mStatusBarState); + pw.println("mStatusBarState=" + StatusBarState.toString(mStatusBarState)); pw.println("mExpansionChanging=" + mExpansionChanging); pw.println("mPanelFullWidth=" + mIsSmallScreen); pw.println("mPulsing=" + mPulsing); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 6a7a5cd8f6c3..fedb88d29837 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -113,6 +113,9 @@ import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.StackScrollerDecorView; import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation; import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor; +import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds; +import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape; +import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; @@ -135,6 +138,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.Callable; import java.util.function.BiConsumer; @@ -143,7 +147,9 @@ import java.util.function.Consumer; /** * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack. */ -public class NotificationStackScrollLayout extends ViewGroup implements Dumpable { +public class NotificationStackScrollLayout + extends ViewGroup + implements Dumpable, NotificationScrollView { public static final float BACKGROUND_ALPHA_DIMMED = 0.7f; private static final String TAG = "StackScroller"; @@ -218,6 +224,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable */ private final StackScrollAlgorithm mStackScrollAlgorithm; private final AmbientState mAmbientState; + private final ScrollViewFields mScrollViewFields = new ScrollViewFields(); private final GroupMembershipManager mGroupMembershipManager; private final GroupExpansionManager mGroupExpansionManager; @@ -590,7 +597,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @Override public boolean isScrolledToTop() { if (SceneContainerFlag.isEnabled()) { - return mController.isPlaceholderScrolledToTop(); + return mScrollViewFields.isScrolledToTop(); } else { return mOwnScrollY == 0; } @@ -855,7 +862,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable drawDebugInfo(canvas, y, Color.MAGENTA, /* label= */ "mContentHeight = " + y); - drawDebugInfo(canvas, mRoundedRectClippingBottom, Color.DKGRAY, + y = mRoundedRectClippingBottom; + drawDebugInfo(canvas, y, Color.DKGRAY, /* label= */ "mRoundedRectClippingBottom) = " + y); } @@ -1131,6 +1139,48 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } + @Override + public View asView() { + return this; + } + + @Override + public void setScrolledToTop(boolean scrolledToTop) { + mScrollViewFields.setScrolledToTop(scrolledToTop); + } + + @Override + public void setStackTop(float stackTop) { + mScrollViewFields.setStackTop(stackTop); + // TODO(b/332574413): replace the following with using stackTop + updateTopPadding(stackTop, isAddOrRemoveAnimationPending()); + } + + @Override + public void setStackBottom(float stackBottom) { + mScrollViewFields.setStackBottom(stackBottom); + } + + @Override + public void setHeadsUpTop(float headsUpTop) { + mScrollViewFields.setHeadsUpTop(headsUpTop); + } + + @Override + public void setSyntheticScrollConsumer(@Nullable Consumer<Float> consumer) { + mScrollViewFields.setSyntheticScrollConsumer(consumer); + } + + @Override + public void setStackHeightConsumer(@Nullable Consumer<Float> consumer) { + mScrollViewFields.setStackHeightConsumer(consumer); + } + + @Override + public void setHeadsUpHeightConsumer(@Nullable Consumer<Float> consumer) { + mScrollViewFields.setHeadsUpHeightConsumer(consumer); + } + /** * @param listener to be notified after the location of Notification children might have * changed. @@ -1381,6 +1431,31 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mOnStackYChanged = onStackYChanged; } + @Override + public void setExpandFraction(float expandFraction) { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return; + final float oldFraction = mAmbientState.getExpansionFraction(); + final boolean wasExpanding = oldFraction != 0f && oldFraction != 1f; + final boolean nowExpanding = expandFraction != 0f && expandFraction != 1f; + + // need to enter 'expanding' state before handling the new expand fraction, and then + if (nowExpanding && !wasExpanding) { + onExpansionStarted(); + mController.checkSnoozeLeavebehind(); + } + + // Update the expand progress between started/stopped events + mAmbientState.setExpansionFraction(expandFraction); + // TODO(b/332577544): don't convert to height which then converts to the fraction again + setExpandedHeight(expandFraction * getHeight()); + + // expansion stopped event requires that the expandFraction has already been updated + if (!nowExpanding && wasExpanding) { + setCheckForLeaveBehind(false); + onExpansionStopped(); + } + } + /** * Update the height of the panel. * @@ -2315,7 +2390,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable /* notificationStackScrollLayout= */ this, mMaxDisplayedNotifications, shelfIntrinsicHeight); mIntrinsicContentHeight = height; - mController.setIntrinsicContentHeight(mIntrinsicContentHeight); + mScrollViewFields.sendStackHeight(height); // The topPadding can be bigger than the regular padding when qs is expanded, in that // state the maxPanelHeight and the contentHeight should be bigger @@ -3555,7 +3630,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable protected boolean isInsideQsHeader(MotionEvent ev) { if (SceneContainerFlag.isEnabled()) { - return ev.getY() < mController.getPlaceholderTop(); + return ev.getY() < mScrollViewFields.getScrimClippingShape().getBounds().getTop(); } mQsHeader.getBoundsOnScreen(mQsHeaderBound); @@ -4088,7 +4163,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable // to it so that it can scroll the stack and scrim accordingly. if (SceneContainerFlag.isEnabled()) { float diff = endPosition - layoutEnd; - mController.sendSyntheticScrollToSceneFramework(diff); + mScrollViewFields.sendSyntheticScroll(diff); } setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd)); mDisallowScrollingInThisMotion = true; @@ -4971,6 +5046,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable elapsedRealtime - mLastUpdateSidePaddingElapsedRealtime); println(pw, "isSmallLandscapeLockscreenEnabled", mIsSmallLandscapeLockscreenEnabled); mNotificationStackSizeCalculator.dump(pw, args); + mScrollViewFields.dump(pw); }); pw.println(); pw.println("Contents:"); @@ -5511,8 +5587,40 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable /** * Set rounded rect clipping bounds on this view. */ + @Override + public void setScrimClippingShape(@Nullable ShadeScrimShape shape) { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return; + if (Objects.equals(mScrollViewFields.getScrimClippingShape(), shape)) return; + mScrollViewFields.setScrimClippingShape(shape); + mShouldUseRoundedRectClipping = shape != null; + mRoundedClipPath.reset(); + if (shape != null) { + ShadeScrimBounds bounds = shape.getBounds(); + mRoundedRectClippingLeft = (int) bounds.getLeft(); + mRoundedRectClippingTop = (int) bounds.getTop(); + mRoundedRectClippingRight = (int) bounds.getRight(); + mRoundedRectClippingBottom = (int) bounds.getBottom(); + mBgCornerRadii[0] = shape.getTopRadius(); + mBgCornerRadii[1] = shape.getTopRadius(); + mBgCornerRadii[2] = shape.getTopRadius(); + mBgCornerRadii[3] = shape.getTopRadius(); + mBgCornerRadii[4] = shape.getBottomRadius(); + mBgCornerRadii[5] = shape.getBottomRadius(); + mBgCornerRadii[6] = shape.getBottomRadius(); + mBgCornerRadii[7] = shape.getBottomRadius(); + mRoundedClipPath.addRoundRect( + bounds.getLeft(), bounds.getTop(), bounds.getRight(), bounds.getBottom(), + mBgCornerRadii, Path.Direction.CW); + } + invalidate(); + } + + /** + * Set rounded rect clipping bounds on this view. + */ public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius, int bottomRadius) { + SceneContainerFlag.assertInLegacyMode(); if (mRoundedRectClippingLeft == left && mRoundedRectClippingRight == right && mRoundedRectClippingBottom == bottom && mRoundedRectClippingTop == top && mBgCornerRadii[0] == topRadius && mBgCornerRadii[5] == bottomRadius) { @@ -5590,6 +5698,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * Should we use rounded rect clipping */ private void updateUseRoundedRectClipping() { + if (SceneContainerFlag.isEnabled()) return; // We don't want to clip notifications when QS is expanded, because incoming heads up on // the bottom would be clipped otherwise boolean qsAllowsClipping = mQsExpansionFraction < 0.5f || mShouldUseSplitNotificationShade; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 59901aca0425..06479e5a8e0e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -83,6 +83,7 @@ import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.scene.ui.view.WindowRootView; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeViewController; @@ -125,7 +126,6 @@ import com.android.systemui.statusbar.notification.row.NotificationGuts; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.NotificationSnooze; import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor; -import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor; import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; @@ -189,7 +189,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { private final VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator; private final ShadeController mShadeController; private final Provider<WindowRootView> mWindowRootView; - private final NotificationStackAppearanceInteractor mStackAppearanceInteractor; private final KeyguardMediaController mKeyguardMediaController; private final SysuiStatusBarStateController mStatusBarStateController; private final KeyguardBypassController mKeyguardBypassController; @@ -742,7 +741,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { NotificationListViewBinder viewBinder, ShadeController shadeController, Provider<WindowRootView> windowRootView, - NotificationStackAppearanceInteractor stackAppearanceInteractor, InteractionJankMonitor jankMonitor, StackStateLogger stackLogger, NotificationStackScrollLogger logger, @@ -795,7 +793,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { mSeenNotificationsInteractor = seenNotificationsInteractor; mShadeController = shadeController; mWindowRootView = windowRootView; - mStackAppearanceInteractor = stackAppearanceInteractor; mFeatureFlags = featureFlags; mNotificationTargetsHelper = notificationTargetsHelper; mSecureSettings = secureSettings; @@ -1124,6 +1121,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { } public boolean isAddOrRemoveAnimationPending() { + SceneContainerFlag.assertInLegacyMode(); return mView != null && mView.isAddOrRemoveAnimationPending(); } @@ -1163,29 +1161,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { } } - /** Send internal notification expansion to the scene container framework. */ - public void sendSyntheticScrollToSceneFramework(Float delta) { - mStackAppearanceInteractor.setSyntheticScroll(delta); - } - - /** Get the y-coordinate of the top bound of the stack. */ - public float getPlaceholderTop() { - return mStackAppearanceInteractor.getShadeScrimBounds().getValue().getTop(); - } - - /** - * Returns whether the notification stack is scrolled to the top; i.e., it cannot be scrolled - * down any further. - */ - public boolean isPlaceholderScrolledToTop() { - return mStackAppearanceInteractor.getScrolledToTop().getValue(); - } - - /** Set the intrinsic height of the stack content without additional padding. */ - public void setIntrinsicContentHeight(float intrinsicContentHeight) { - mStackAppearanceInteractor.setStackHeight(intrinsicContentHeight); - } - public void setIntrinsicPadding(int intrinsicPadding) { mView.setIntrinsicPadding(intrinsicPadding); } @@ -1264,6 +1239,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { } public void setScrollingEnabled(boolean enabled) { + SceneContainerFlag.assertInLegacyMode(); mView.setScrollingEnabled(enabled); } @@ -1284,6 +1260,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { } public void updateTopPadding(float qsHeight, boolean animate) { + SceneContainerFlag.assertInLegacyMode(); mView.updateTopPadding(qsHeight, animate); } @@ -1386,11 +1363,13 @@ public class NotificationStackScrollLayoutController implements Dumpable { } public void onExpansionStarted() { + SceneContainerFlag.assertInLegacyMode(); mView.onExpansionStarted(); checkSnoozeLeavebehind(); } public void onExpansionStopped() { + SceneContainerFlag.assertInLegacyMode(); mView.setCheckForLeaveBehind(false); mView.onExpansionStopped(); } @@ -1519,6 +1498,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { } public void setExpandedHeight(float expandedHeight) { + SceneContainerFlag.assertInLegacyMode(); mView.setExpandedHeight(expandedHeight); } @@ -1794,6 +1774,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { */ public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius, int bottomRadius) { + SceneContainerFlag.assertInLegacyMode(); mView.setRoundedClippingBounds(left, top, right, bottom, topRadius, bottomRadius); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt new file mode 100644 index 000000000000..edac5ede1e91 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.stack + +import android.util.IndentingPrintWriter +import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape +import com.android.systemui.util.printSection +import com.android.systemui.util.println +import java.util.function.Consumer + +/** + * This is a state holder object used by [NSSL][NotificationStackScrollLayout] to contain states + * provided by the `NotificationScrollViewBinder` to the `NotificationScrollView`. + * + * Unlike AmbientState, no class other than NSSL should ever have access to this class in any way. + * These fields are effectively NSSL's private fields. + */ +class ScrollViewFields { + /** Used to produce the clipping path */ + var scrimClippingShape: ShadeScrimShape? = null + /** Y coordinate in view pixels of the top of the notification stack */ + var stackTop: Float = 0f + /** + * Y coordinate in view pixels above which the bottom of the notification stack / shelf / footer + * must be. + */ + var stackBottom: Float = 0f + /** Y coordinate in view pixels of the top of the HUN */ + var headsUpTop: Float = 0f + /** Whether the notifications are scrolled all the way to the top (i.e. when freshly opened) */ + var isScrolledToTop: Boolean = true + + /** + * When internal NSSL expansion requires the stack to be scrolled (e.g. to keep an expanding + * notification in view), that scroll amount can be sent here and it will be handled by the + * placeholder + */ + var syntheticScrollConsumer: Consumer<Float>? = null + /** + * Any time the stack height is recalculated, it should be updated here to be used by the + * placeholder + */ + var stackHeightConsumer: Consumer<Float>? = null + /** + * Any time the heads up height is recalculated, it should be updated here to be used by the + * placeholder + */ + var headsUpHeightConsumer: Consumer<Float>? = null + + /** send the [syntheticScroll] to the [syntheticScrollConsumer], if present. */ + fun sendSyntheticScroll(syntheticScroll: Float) = + syntheticScrollConsumer?.accept(syntheticScroll) + /** send the [stackHeight] to the [stackHeightConsumer], if present. */ + fun sendStackHeight(stackHeight: Float) = stackHeightConsumer?.accept(stackHeight) + /** send the [headsUpHeight] to the [headsUpHeightConsumer], if present. */ + fun sendHeadsUpHeight(headsUpHeight: Float) = headsUpHeightConsumer?.accept(headsUpHeight) + + fun dump(pw: IndentingPrintWriter) { + pw.printSection("StackViewStates") { + pw.println("scrimClippingShape", scrimClippingShape) + pw.println("stackTop", stackTop) + pw.println("stackBottom", stackBottom) + pw.println("headsUpTop", headsUpTop) + pw.println("isScrolledToTop", isScrolledToTop) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt index 462547ea2385..b04737936166 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt @@ -27,8 +27,12 @@ import kotlinx.coroutines.flow.MutableStateFlow */ @SysUISingleton class NotificationPlaceholderRepository @Inject constructor() { - /** The bounds of the notification shade scrim / container in the current scene. */ - val shadeScrimBounds = MutableStateFlow(ShadeScrimBounds()) + /** + * The bounds of the notification shade scrim / container in the current scene. + * + * When `null`, clipping should not be applied to notifications. + */ + val shadeScrimBounds = MutableStateFlow<ShadeScrimBounds?>(null) /** * The y-coordinate in px of top of the contents of the notification stack. This value can be @@ -44,7 +48,7 @@ class NotificationPlaceholderRepository @Inject constructor() { val headsUpTop = MutableStateFlow(0f) /** height made available to the notifications in the size-constrained mode of lock screen. */ - val constrainedAvailableSpace = MutableStateFlow(0f) + val constrainedAvailableSpace = MutableStateFlow(0) /** * Whether the notification stack is scrolled to the top; i.e., it cannot be scrolled down any diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt index 938e43e4a2d4..8a9da69079d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt @@ -35,7 +35,7 @@ class NotificationViewHeightRepository @Inject constructor() { val stackHeight = MutableStateFlow(0f) /** The height in px of the current heads up notification. */ - val activeHeadsUpRowHeight = MutableStateFlow(0f) + val headsUpHeight = MutableStateFlow(0f) /** * The amount in px that the notification stack should scroll due to internal expansion. This diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt index 32562f109252..a5b4f5f2bb4f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt @@ -42,7 +42,7 @@ constructor( shadeInteractor: ShadeInteractor, ) { /** The bounds of the notification stack in the current scene. */ - val shadeScrimBounds: StateFlow<ShadeScrimBounds> = + val shadeScrimBounds: StateFlow<ShadeScrimBounds?> = placeholderRepository.shadeScrimBounds.asStateFlow() /** @@ -59,8 +59,8 @@ constructor( isExpandingFromHeadsUp, ) { shadeMode, isExpandingFromHeadsUp -> ShadeScrimRounding( - roundTop = !(shadeMode == ShadeMode.Split && isExpandingFromHeadsUp), - roundBottom = shadeMode != ShadeMode.Single, + isTopRounded = !(shadeMode == ShadeMode.Split && isExpandingFromHeadsUp), + isBottomRounded = shadeMode != ShadeMode.Single, ) } .distinctUntilChanged() @@ -72,15 +72,28 @@ constructor( */ val stackHeight: StateFlow<Float> = viewHeightRepository.stackHeight.asStateFlow() + /** The height in px of the contents of the HUN. */ + val headsUpHeight: StateFlow<Float> = viewHeightRepository.headsUpHeight.asStateFlow() + /** The y-coordinate in px of top of the contents of the notification stack. */ val stackTop: StateFlow<Float> = placeholderRepository.stackTop.asStateFlow() + /** The y-coordinate in px of bottom of the contents of the notification stack. */ + val stackBottom: StateFlow<Float> = placeholderRepository.stackBottom.asStateFlow() + + /** The height of the keyguard's available space bounds */ + val constrainedAvailableSpace: StateFlow<Int> = + placeholderRepository.constrainedAvailableSpace.asStateFlow() + /** * Whether the notification stack is scrolled to the top; i.e., it cannot be scrolled down any * further. */ val scrolledToTop: StateFlow<Boolean> = placeholderRepository.scrolledToTop.asStateFlow() + /** The y-coordinate in px of bottom of the contents of the HUN. */ + val headsUpTop: StateFlow<Float> = placeholderRepository.headsUpTop.asStateFlow() + /** * The amount in px that the notification stack should scroll due to internal expansion. This * should only happen when a notification expansion hits the bottom of the screen, so it is @@ -89,8 +102,8 @@ constructor( val syntheticScroll: Flow<Float> = viewHeightRepository.syntheticScroll.asStateFlow() /** Sets the position of the notification stack in the current scene. */ - fun setShadeScrimBounds(bounds: ShadeScrimBounds) { - check(bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" } + fun setShadeScrimBounds(bounds: ShadeScrimBounds?) { + check(bounds == null || bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" } placeholderRepository.shadeScrimBounds.value = bounds } @@ -99,9 +112,19 @@ constructor( viewHeightRepository.stackHeight.value = height } + /** Sets the height of heads up notification. */ + fun setHeadsUpHeight(height: Float) { + viewHeightRepository.headsUpHeight.value = height + } + /** Sets the y-coord in px of the top of the contents of the notification stack. */ - fun setStackTop(startY: Float) { - placeholderRepository.stackTop.value = startY + fun setStackTop(stackTop: Float) { + placeholderRepository.stackTop.value = stackTop + } + + /** Sets the y-coord in px of the bottom of the contents of the notification stack. */ + fun setStackBottom(stackBottom: Float) { + placeholderRepository.stackBottom.value = stackBottom } /** Sets whether the notification stack is scrolled to the top. */ @@ -113,4 +136,12 @@ constructor( fun setSyntheticScroll(delta: Float) { viewHeightRepository.syntheticScroll.value = delta } + + fun setConstrainedAvailableSpace(height: Int) { + placeholderRepository.constrainedAvailableSpace.value = height + } + + fun setHeadsUpTop(headsUpTop: Float) { + placeholderRepository.headsUpTop.value = headsUpTop + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt index 448127a55b89..7e3e724bfed9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt @@ -29,4 +29,12 @@ data class ShadeScrimBounds( ) { /** The current height of the notification container. */ val height: Float = bottom - top + + operator fun minus(position: ViewPosition) = + ShadeScrimBounds( + left = left - position.left, + top = top - position.top, + right = right - position.left, + bottom = bottom - position.top, + ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimClipping.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimClipping.kt index 621dd0c49f54..e86fd1c7cdc7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimClipping.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimClipping.kt @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.notification.stack.shared.model -/** Models the clipping rounded rectangle of the notification stack */ +/** Models the clipping rounded rectangle of the notification stack, where the rounding is binary */ data class ShadeScrimClipping( val bounds: ShadeScrimBounds = ShadeScrimBounds(), val rounding: ShadeScrimRounding = ShadeScrimRounding() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimRounding.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimRounding.kt index 2fe265f35604..9d4231c8967b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimRounding.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimRounding.kt @@ -19,7 +19,7 @@ package com.android.systemui.statusbar.notification.stack.shared.model /** Models the corner rounds of the notification stack. */ data class ShadeScrimRounding( /** Whether the top corners of the notification stack should be rounded. */ - val roundTop: Boolean = false, + val isTopRounded: Boolean = false, /** Whether the bottom corners of the notification stack should be rounded. */ - val roundBottom: Boolean = false, + val isBottomRounded: Boolean = false, ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimShape.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimShape.kt new file mode 100644 index 000000000000..e6f0647a059d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimShape.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.stack.shared.model + +/** Models the clipping rounded rectangle of the notification stack, with corner radii in px */ +data class ShadeScrimShape( + val bounds: ShadeScrimBounds = ShadeScrimBounds(), + /** radius in px of the top corners */ + val topRadius: Int, + /** radius in px of the bottom corners */ + val bottomRadius: Int, +) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt new file mode 100644 index 000000000000..5d2b0ad2b0a6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.stack.shared.model + +/** An offset of view, used to adjust bounds. */ +data class ViewPosition(val left: Int = 0, val top: Int = 0) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt new file mode 100644 index 000000000000..ac00d3b27493 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.stack.ui.view + +import android.view.View +import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape +import java.util.function.Consumer + +/** + * This view (interface) is the view which scrolls and positions the heads up notification and + * notification stack, but is otherwise agnostic to the content. + */ +interface NotificationScrollView { + /** + * Since this is an interface rather than a literal View, this provides cast-like access to the + * underlying view. + */ + fun asView(): View + + /** Set the clipping bounds used when drawing */ + fun setScrimClippingShape(shape: ShadeScrimShape?) + + /** set the y position in px of the top of the stack in this view's coordinates */ + fun setStackTop(stackTop: Float) + + /** set the y position in px of the bottom of the stack in this view's coordinates */ + fun setStackBottom(stackBottom: Float) + + /** set the y position in px of the top of the HUN in this view's coordinates */ + fun setHeadsUpTop(headsUpTop: Float) + + /** set whether the view has been scrolled all the way to the top */ + fun setScrolledToTop(scrolledToTop: Boolean) + + /** Set a consumer for synthetic scroll events */ + fun setSyntheticScrollConsumer(consumer: Consumer<Float>?) + /** Set a consumer for stack height changed events */ + fun setStackHeightConsumer(consumer: Consumer<Float>?) + /** Set a consumer for heads up height changed events */ + fun setHeadsUpHeightConsumer(consumer: Consumer<Float>?) + + /** sets that scrolling is allowed */ + fun setScrollingEnabled(enabled: Boolean) + + /** sets the current expand fraction */ + fun setExpandFraction(expandFraction: Float) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt new file mode 100644 index 000000000000..a98717a743d8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.stack.ui.viewbinder + +import android.view.View +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.common.ui.ConfigurationState +import com.android.systemui.common.ui.view.onLayoutChanged +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.dump.DumpManager +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.res.R +import com.android.systemui.statusbar.notification.stack.shared.model.ViewPosition +import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationScrollViewModel +import com.android.systemui.util.kotlin.FlowDumperImpl +import com.android.systemui.util.kotlin.launchAndDispose +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.DisposableHandle +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch + +/** Binds the [NotificationScrollView]. */ +@SysUISingleton +class NotificationScrollViewBinder +@Inject +constructor( + dumpManager: DumpManager, + @Main private val mainImmediateDispatcher: CoroutineDispatcher, + private val view: NotificationScrollView, + private val viewModel: NotificationScrollViewModel, + private val configuration: ConfigurationState, +) : FlowDumperImpl(dumpManager) { + + private val viewPosition = MutableStateFlow(ViewPosition()).dumpValue("viewPosition") + private val viewTopOffset = viewPosition.map { it.top }.distinctUntilChanged() + + fun bindWhileAttached(): DisposableHandle { + return view.asView().repeatWhenAttached(mainImmediateDispatcher) { + repeatOnLifecycle(Lifecycle.State.CREATED) { bind() } + } + } + + suspend fun bind() = coroutineScope { + launchAndDispose { + viewPosition.value = view.asView().position + view.asView().onLayoutChanged { viewPosition.value = it.position } + } + + launch { + viewModel.shadeScrimShape(scrimRadius, viewPosition).collect { + view.setScrimClippingShape(it) + } + } + + launch { viewModel.stackTop.minusTopOffset().collect { view.setStackTop(it) } } + launch { viewModel.stackBottom.minusTopOffset().collect { view.setStackBottom(it) } } + launch { viewModel.scrolledToTop.collect { view.setScrolledToTop(it) } } + launch { viewModel.headsUpTop.minusTopOffset().collect { view.setHeadsUpTop(it) } } + launch { viewModel.expandFraction.collect { view.setExpandFraction(it) } } + launch { viewModel.isScrollable.collect { view.setScrollingEnabled(it) } } + + launchAndDispose { + view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer) + view.setStackHeightConsumer(viewModel.stackHeightConsumer) + view.setHeadsUpHeightConsumer(viewModel.headsUpHeightConsumer) + DisposableHandle { + view.setSyntheticScrollConsumer(null) + view.setStackHeightConsumer(null) + view.setHeadsUpHeightConsumer(null) + } + } + } + + /** Combine with the topOffset flow and subtract that value from this flow's value */ + private fun Flow<Float>.minusTopOffset() = + combine(viewTopOffset) { y, topOffset -> y - topOffset } + + /** flow of the scrim clipping radius */ + private val scrimRadius: Flow<Int> + get() = configuration.getDimensionPixelOffset(R.dimen.notification_scrim_corner_radius) + + /** Construct a [ViewPosition] from this view using [View.getLeft] and [View.getTop] */ + private val View.position + get() = ViewPosition(left = left, top = top) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackViewBinder.kt deleted file mode 100644 index d6d31db86e61..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackViewBinder.kt +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.stack.ui.viewbinder - -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.common.ui.ConfigurationState -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.res.R -import com.android.systemui.statusbar.notification.stack.AmbientState -import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout -import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController -import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel -import javax.inject.Inject -import kotlin.math.roundToInt -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.DisposableHandle -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.launch - -/** Binds the NSSL/Controller/AmbientState to their ViewModel. */ -@SysUISingleton -class NotificationStackViewBinder -@Inject -constructor( - @Main private val mainImmediateDispatcher: CoroutineDispatcher, - private val ambientState: AmbientState, - private val view: NotificationStackScrollLayout, - private val controller: NotificationStackScrollLayoutController, - private val viewModel: NotificationStackAppearanceViewModel, - private val configuration: ConfigurationState, -) { - - fun bindWhileAttached(): DisposableHandle { - return view.repeatWhenAttached(mainImmediateDispatcher) { - repeatOnLifecycle(Lifecycle.State.CREATED) { bind() } - } - } - - suspend fun bind() = coroutineScope { - launch { - combine(viewModel.shadeScrimClipping, clipRadius, ::Pair).collect { - (clipping, clipRadius) -> - val (bounds, rounding) = clipping - val viewLeft = controller.view.left - val viewTop = controller.view.top - controller.setRoundedClippingBounds( - bounds.left.roundToInt() - viewLeft, - bounds.top.roundToInt() - viewTop, - bounds.right.roundToInt() - viewLeft, - bounds.bottom.roundToInt() - viewTop, - if (rounding.roundTop) clipRadius else 0, - if (rounding.roundBottom) clipRadius else 0, - ) - } - } - - launch { - viewModel.stackTop.collect { - controller.updateTopPadding(it, controller.isAddOrRemoveAnimationPending) - } - } - - launch { - var wasExpanding = false - viewModel.expandFraction.collect { expandFraction -> - val nowExpanding = expandFraction != 0f && expandFraction != 1f - if (nowExpanding && !wasExpanding) { - controller.onExpansionStarted() - } - ambientState.expansionFraction = expandFraction - controller.expandedHeight = expandFraction * controller.view.height - if (!nowExpanding && wasExpanding) { - controller.onExpansionStopped() - } - wasExpanding = nowExpanding - } - } - - launch { viewModel.isScrollable.collect { controller.setScrollingEnabled(it) } } - } - - private val clipRadius: Flow<Int> - get() = configuration.getDimensionPixelOffset(R.dimen.notification_scrim_corner_radius) -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt index 4a096a89848a..cfd19bacdc69 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt @@ -49,7 +49,7 @@ constructor( private val sceneContainerFlags: SceneContainerFlags, private val controller: NotificationStackScrollLayoutController, private val notificationStackSizeCalculator: NotificationStackSizeCalculator, - private val notificationStackViewBinder: NotificationStackViewBinder, + private val notificationScrollViewBinder: NotificationScrollViewBinder, @Main private val mainImmediateDispatcher: CoroutineDispatcher, ) { @@ -162,7 +162,7 @@ constructor( } if (sceneContainerFlags.isEnabled()) { - disposables += notificationStackViewBinder.bindWhileAttached() + disposables += notificationScrollViewBinder.bindWhileAttached() } controller.setOnHeightChangedRunnable { viewModel.notificationStackChanged() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt index 071127c03202..9483f3310316 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt @@ -26,17 +26,18 @@ import com.android.systemui.scene.shared.model.Scenes.Shade import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimClipping +import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape +import com.android.systemui.statusbar.notification.stack.shared.model.ViewPosition import com.android.systemui.util.kotlin.FlowDumperImpl import javax.inject.Inject import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map /** ViewModel which represents the state of the NSSL/Controller in the world of flexiglass */ @SysUISingleton -class NotificationStackAppearanceViewModel +class NotificationScrollViewModel @Inject constructor( dumpManager: DumpManager, @@ -80,16 +81,48 @@ constructor( .dumpWhileCollecting("expandFraction") /** The bounds of the notification stack in the current scene. */ - val shadeScrimClipping: Flow<ShadeScrimClipping> = + private val shadeScrimClipping: Flow<ShadeScrimClipping?> = combine( stackAppearanceInteractor.shadeScrimBounds, stackAppearanceInteractor.shadeScrimRounding, - ::ShadeScrimClipping - ) + ) { bounds, rounding -> + bounds?.let { ShadeScrimClipping(it, rounding) } + } .dumpWhileCollecting("stackClipping") + fun shadeScrimShape( + cornerRadius: Flow<Int>, + viewPosition: Flow<ViewPosition> + ): Flow<ShadeScrimShape?> = + combine(shadeScrimClipping, cornerRadius, viewPosition) { clipping, radius, position -> + if (clipping == null) return@combine null + ShadeScrimShape( + bounds = clipping.bounds - position, + topRadius = radius.takeIf { clipping.rounding.isTopRounded } ?: 0, + bottomRadius = radius.takeIf { clipping.rounding.isBottomRounded } ?: 0 + ) + } + .dumpWhileCollecting("shadeScrimShape") + /** The y-coordinate in px of top of the contents of the notification stack. */ - val stackTop: StateFlow<Float> = stackAppearanceInteractor.stackTop.dumpValue("stackTop") + val stackTop: Flow<Float> = stackAppearanceInteractor.stackTop.dumpValue("stackTop") + /** The y-coordinate in px of bottom of the contents of the notification stack. */ + val stackBottom: Flow<Float> = stackAppearanceInteractor.stackBottom.dumpValue("stackBottom") + /** + * Whether the notification stack is scrolled to the top; i.e., it cannot be scrolled down any + * further. + */ + val scrolledToTop: Flow<Boolean> = + stackAppearanceInteractor.scrolledToTop.dumpValue("scrolledToTop") + /** The y-coordinate in px of bottom of the contents of the HUN. */ + val headsUpTop: Flow<Float> = stackAppearanceInteractor.headsUpTop.dumpValue("headsUpTop") + + /** Receives the amount (px) that the stack should scroll due to internal expansion. */ + val syntheticScrollConsumer: (Float) -> Unit = stackAppearanceInteractor::setSyntheticScroll + /** Receives the height of the contents of the notification stack. */ + val stackHeightConsumer: (Float) -> Unit = stackAppearanceInteractor::setStackHeight + /** Receives the height of the heads up notification. */ + val headsUpHeightConsumer: (Float) -> Unit = stackAppearanceInteractor::setHeadsUpHeight /** Whether the notification stack is scrollable or not. */ val isScrollable: Flow<Boolean> = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt index 477f139bcd7d..b284179d42b7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor @@ -26,8 +27,10 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding +import com.android.systemui.util.kotlin.FlowDumperImpl import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow /** * ViewModel used by the Notification placeholders inside the scene container to update the @@ -37,67 +40,72 @@ import kotlinx.coroutines.flow.Flow class NotificationsPlaceholderViewModel @Inject constructor( + dumpManager: DumpManager, private val interactor: NotificationStackAppearanceInteractor, shadeInteractor: ShadeInteractor, flags: SceneContainerFlags, featureFlags: FeatureFlagsClassic, private val keyguardInteractor: KeyguardInteractor, -) { +) : FlowDumperImpl(dumpManager) { /** DEBUG: whether the placeholder should be made slightly visible for positional debugging. */ val isVisualDebuggingEnabled: Boolean = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES) /** DEBUG: whether the debug logging should be output. */ val isDebugLoggingEnabled: Boolean = flags.isEnabled() - /** - * Notifies that the bounds of the notification placeholder have changed. - * - * @param top The position of the top of the container in its window coordinate system, in - * pixels. - * @param bottom The position of the bottom of the container in its window coordinate system, in - * pixels. - */ - fun onBoundsChanged( - left: Float, + /** Notifies that the bounds of the notification scrim have changed. */ + fun onScrimBoundsChanged(bounds: ShadeScrimBounds?) { + interactor.setShadeScrimBounds(bounds) + } + + /** Notifies that the bounds of the notification placeholder have changed. */ + fun onStackBoundsChanged( top: Float, - right: Float, bottom: Float, ) { keyguardInteractor.setNotificationContainerBounds( NotificationContainerBounds(top = top, bottom = bottom) ) - interactor.setShadeScrimBounds( - ShadeScrimBounds(top = top, bottom = bottom, left = left, right = right) - ) + interactor.setStackTop(top) + interactor.setStackBottom(bottom) + } + + /** Sets the available space */ + fun onConstrainedAvailableSpaceChanged(height: Int) { + interactor.setConstrainedAvailableSpace(height) + } + + fun onHeadsUpTopChanged(headsUpTop: Float) { + interactor.setHeadsUpTop(headsUpTop) } /** Corner rounding of the stack */ - val shadeScrimRounding: Flow<ShadeScrimRounding> = interactor.shadeScrimRounding + val shadeScrimRounding: Flow<ShadeScrimRounding> = + interactor.shadeScrimRounding.dumpWhileCollecting("shadeScrimRounding") /** * The height in px of the contents of notification stack. Depending on the number of * notifications, this can exceed the space available on screen to show notifications, at which * point the notification stack should become scrollable. */ - val stackHeight = interactor.stackHeight + val stackHeight: StateFlow<Float> = interactor.stackHeight.dumpValue("stackHeight") + + /** The height in px of the contents of the HUN. */ + val headsUpHeight: StateFlow<Float> = interactor.headsUpHeight.dumpValue("headsUpHeight") /** * The amount [0-1] that the shade has been opened. At 0, the shade is closed; at 1, the shade * is open. */ - val expandFraction: Flow<Float> = shadeInteractor.shadeExpansion + val expandFraction: Flow<Float> = shadeInteractor.shadeExpansion.dumpValue("expandFraction") /** * The amount in px that the notification stack should scroll due to internal expansion. This * should only happen when a notification expansion hits the bottom of the screen, so it is * necessary to scroll up to keep expanding the notification. */ - val syntheticScroll: Flow<Float> = interactor.syntheticScroll - - /** Sets the y-coord in px of the top of the contents of the notification stack. */ - fun onContentTopChanged(padding: Float) { - interactor.setStackTop(padding) - } + val syntheticScroll: Flow<Float> = + interactor.syntheticScroll.dumpWhileCollecting("syntheticScroll") /** Sets whether the notification stack is scrolled to the top. */ fun setScrolledToTop(scrolledToTop: Boolean) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index 9f576066cc98..692368de5aea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -60,7 +60,9 @@ import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransition import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor import com.android.systemui.util.kotlin.BooleanFlowOperators.and import com.android.systemui.util.kotlin.BooleanFlowOperators.or @@ -97,6 +99,7 @@ constructor( private val keyguardInteractor: KeyguardInteractor, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val shadeInteractor: ShadeInteractor, + private val notificationStackAppearanceInteractor: NotificationStackAppearanceInteractor, private val alternateBouncerToGoneTransitionViewModel: AlternateBouncerToGoneTransitionViewModel, private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, @@ -364,6 +367,10 @@ constructor( initialValue = NotificationContainerBounds(), ) .dumpValue("bounds") + get() { + /* check if */ SceneContainerFlag.isUnexpectedlyInLegacyMode() + return field + } /** * Ensure view is visible when the shade/qs are expanded. Also, as QS is expanding, fade out @@ -571,8 +578,11 @@ constructor( .dumpWhileCollecting("translationX") private val availableHeight: Flow<Float> = - bounds - .map { it.bottom - it.top } + if (SceneContainerFlag.isEnabled) { + notificationStackAppearanceInteractor.constrainedAvailableSpace.map { it.toFloat() } + } else { + bounds.map { it.bottom - it.top } + } .distinctUntilChanged() .dumpWhileCollecting("availableHeight") diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/DisposableHandleExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/DisposableHandleExt.kt index 909a18be4b9e..97d957dde01b 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/DisposableHandleExt.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/DisposableHandleExt.kt @@ -17,8 +17,14 @@ package com.android.systemui.util.kotlin import com.android.systemui.lifecycle.repeatWhenAttached +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.DisposableHandle +import kotlinx.coroutines.Job import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.launch /** * Suspends to keep getting updates until cancellation. Once cancelled, mark this as eligible for @@ -42,3 +48,22 @@ suspend fun DisposableHandle.awaitCancellationThenDispose() { dispose() } } + +/** + * This will [launch], run [onLaunch] to get a [DisposableHandle], and finally + * [awaitCancellationThenDispose][DisposableHandle.awaitCancellationThenDispose]. This can be used + * to structure self-disposing code which attaches listeners, for example in ViewBinders: + * ``` + * suspend fun bind(view: MyView, viewModel: MyViewModel) = coroutineScope { + * launchAndDispose { + * view.setOnClickListener { viewModel.handleClick() } + * DisposableHandle { view.setOnClickListener(null) } + * } + * } + * ``` + */ +inline fun CoroutineScope.launchAndDispose( + context: CoroutineContext = EmptyCoroutineContext, + start: CoroutineStart = CoroutineStart.DEFAULT, + crossinline onLaunch: () -> DisposableHandle +): Job = launch(context, start) { onLaunch().awaitCancellationThenDispose() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt index 2ca5aeffcc15..c47f0bcb3192 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt @@ -23,6 +23,7 @@ import android.view.View.GONE import android.view.View.VISIBLE import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest +import com.android.internal.policy.SystemBarUtils import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor @@ -70,7 +71,7 @@ class ClockSectionTest : SysuiTestCase() { Utils.getStatusBarHeaderHeightKeyguard(context) private val LARGE_CLOCK_TOP_WITHOUT_SMARTSPACE = - context.resources.getDimensionPixelSize(R.dimen.status_bar_height) + + SystemBarUtils.getStatusBarHeight(context) + context.resources.getDimensionPixelSize( com.android.systemui.customization.R.dimen.small_clock_padding_top ) + diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index 02f2e16b9570..cf7c6f4e2174 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -20,6 +20,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_SECURE; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; +import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_TRACING; import static com.google.common.truth.Truth.assertThat; @@ -436,6 +437,10 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture()); assertThat((mLayoutParameters.getValue().flags & FLAG_SECURE) != 0).isTrue(); + assertThat( + (mLayoutParameters.getValue().inputFeatures & INPUT_FEATURE_SENSITIVE_FOR_TRACING) + != 0) + .isTrue(); } @Test @@ -444,6 +449,10 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture()); assertThat((mLayoutParameters.getValue().flags & FLAG_SECURE) == 0).isTrue(); + assertThat( + (mLayoutParameters.getValue().inputFeatures & INPUT_FEATURE_SENSITIVE_FOR_TRACING) + == 0) + .isTrue(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index cd8be573a2ac..912ecb340c3c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -94,7 +94,6 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent; import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback; -import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor; import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -161,7 +160,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { @Mock private VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator; @Mock private ShadeController mShadeController; @Mock private Provider<WindowRootView> mWindowRootView; - @Mock private NotificationStackAppearanceInteractor mNotificationStackAppearanceInteractor; private final StackStateLogger mStackLogger = new StackStateLogger(logcatLogBuffer(), logcatLogBuffer()); private final NotificationStackScrollLogger mLogger = new NotificationStackScrollLogger( @@ -1016,7 +1014,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { mViewBinder, mShadeController, mWindowRootView, - mNotificationStackAppearanceInteractor, mKosmos.getInteractionJankMonitor(), mStackLogger, mLogger, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt index 4d902fa35204..df08e4a78178 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt @@ -60,4 +60,8 @@ class FakeQSSceneAdapter( override suspend fun applyBottomNavBarPadding(padding: Int) { _navBarPadding.value = padding } + + override fun requestCloseCustomizer() { + _customizing.value = false + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt index bada2a61995d..10cc13697d96 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt @@ -23,8 +23,8 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor -val Kosmos.notificationStackAppearanceViewModel by Fixture { - NotificationStackAppearanceViewModel( +val Kosmos.notificationScrollViewModel by Fixture { + NotificationScrollViewModel( dumpManager = dumpManager, stackAppearanceInteractor = notificationStackAppearanceInteractor, shadeInteractor = shadeInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt index 29faa58d674a..b2492110f849 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel +import com.android.systemui.dump.dumpManager import com.android.systemui.flags.featureFlagsClassic import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.Kosmos @@ -26,6 +27,7 @@ import com.android.systemui.statusbar.notification.stack.domain.interactor.notif val Kosmos.notificationsPlaceholderViewModel by Fixture { NotificationsPlaceholderViewModel( + dumpManager = dumpManager, interactor = notificationStackAppearanceInteractor, shadeInteractor = shadeInteractor, flags = sceneContainerFlags, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt index de0cc6590593..d2de835ad954 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt @@ -43,6 +43,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -55,6 +56,7 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture { keyguardInteractor = keyguardInteractor, keyguardTransitionInteractor = keyguardTransitionInteractor, shadeInteractor = shadeInteractor, + notificationStackAppearanceInteractor = notificationStackAppearanceInteractor, alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel, aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel, aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel, diff --git a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodAndroidApiTest.java b/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodAndroidApiTest.java deleted file mode 100644 index c11c1bba25a7..000000000000 --- a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodAndroidApiTest.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.platform.test.ravenwood.bivalenttest; - -import static org.junit.Assert.assertEquals; - -import android.util.ArrayMap; -import android.util.Size; - -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.Map; - -// Tests for calling simple Android APIs. -@RunWith(AndroidJUnit4.class) -public class RavenwoodAndroidApiTest { - @Test - public void testArrayMapSimple() { - final Map<String, String> map = new ArrayMap<>(); - - map.put("key1", "value1"); - assertEquals("value1", map.get("key1")); - } - - @Test - public void testSizeSimple() { - final var size = new Size(1, 2); - - assertEquals(2, size.getHeight()); - } -} diff --git a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java b/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java deleted file mode 100644 index 6f2465c406d3..000000000000 --- a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.platform.test.ravenwood.bivalenttest; - -import android.platform.test.annotations.DisabledOnRavenwood; -import android.platform.test.ravenwood.RavenwoodClassRule; -import android.platform.test.ravenwood.RavenwoodRule; - -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Assert; -import org.junit.ClassRule; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -@DisabledOnRavenwood -public class RavenwoodClassRuleDeviceOnlyTest { - @ClassRule - public static final RavenwoodClassRule sRavenwood = new RavenwoodClassRule(); - - @Test - public void testDeviceOnly() { - Assert.assertFalse(RavenwoodRule.isOnRavenwood()); - } -} diff --git a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodClassRuleRavenwoodOnlyTest.java b/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodClassRuleRavenwoodOnlyTest.java deleted file mode 100644 index 21b31d1ca3e8..000000000000 --- a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodClassRuleRavenwoodOnlyTest.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.platform.test.ravenwood.bivalenttest; - -import android.platform.test.ravenwood.RavenwoodClassRule; -import android.platform.test.ravenwood.RavenwoodRule; - -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Assert; -import org.junit.ClassRule; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -// TODO: atest RavenwoodBivalentTest_device fails with the following message. -// `RUNNER ERROR: Instrumentation reported numtests=7 but only ran 6` -// @android.platform.test.annotations.DisabledOnNonRavenwood -// Figure it out and then make DisabledOnNonRavenwood support TYPEs as well. -@Ignore -public class RavenwoodClassRuleRavenwoodOnlyTest { - @ClassRule - public static final RavenwoodClassRule sRavenwood = new RavenwoodClassRule(); - - @Test - public void testRavenwoodOnly() { - Assert.assertTrue(RavenwoodRule.isOnRavenwood()); - } -} diff --git a/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java b/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java index 4ee9a9c94826..2cd585ff6c9c 100644 --- a/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java +++ b/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java @@ -40,7 +40,7 @@ public class RavenwoodTestRunnerValidationTest { public final RuleChain chain = RuleChain.outerRule(mThrown).around(mRavenwood); public RavenwoodTestRunnerValidationTest() { - Assume.assumeTrue(RavenwoodRule._$RavenwoodPrivate.isOptionalValidationEnabled()); + Assume.assumeTrue(mRavenwood._ravenwood_private$isOptionalValidationEnabled()); // Because RavenwoodRule will throw this error before executing the test method, // we can't do it in the test method itself. // So instead, we initialize it here. diff --git a/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java index 9d47f3a6bc5d..8ca34bafc2c6 100644 --- a/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java +++ b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java @@ -31,17 +31,13 @@ import java.lang.annotation.Target; * which means if a test class has this annotation, you can't negate it in subclasses or * on a per-method basis. * - * THIS ANNOTATION CANNOT BE ADDED TO CLASSES AT THIS PONINT. - * See {@link com.android.platform.test.ravenwood.bivalenttest.RavenwoodClassRuleRavenwoodOnlyTest} - * for the reason. - * * The {@code RAVENWOOD_RUN_DISABLED_TESTS} environmental variable won't work because it won't be * propagated to the device. (We may support it in the future, possibly using a debug. sysprop.) * * @hide */ @Inherited -@Target({ElementType.METHOD}) +@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface DisabledOnNonRavenwood { /** diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java index f4b7ec360dbf..9a4d4886d6f0 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java @@ -25,7 +25,6 @@ import static android.platform.test.ravenwood.RavenwoodRule.shouldStillIgnoreInP import android.platform.test.annotations.DisabledOnRavenwood; import android.platform.test.annotations.EnabledOnRavenwood; -import org.junit.Assert; import org.junit.Assume; import org.junit.rules.TestRule; import org.junit.runner.Description; @@ -42,16 +41,27 @@ import org.junit.runners.model.Statement; public class RavenwoodClassRule implements TestRule { @Override public Statement apply(Statement base, Description description) { + // No special treatment when running outside Ravenwood; run tests as-is if (!IS_ON_RAVENWOOD) { - // This should be "Assume", not Assert, but if we use assume here, the device side - // test runner would complain. - // See the TODO comment in RavenwoodClassRuleRavenwoodOnlyTest. - Assert.assertTrue(shouldEnableOnDevice(description)); - } else if (ENABLE_PROBE_IGNORED) { + Assume.assumeTrue(shouldEnableOnDevice(description)); + return base; + } + + if (ENABLE_PROBE_IGNORED) { Assume.assumeFalse(shouldStillIgnoreInProbeIgnoreMode(description)); + // Pass through to possible underlying RavenwoodRule for both environment + // configuration and handling method-level annotations + return base; } else { - Assume.assumeTrue(shouldEnableOnRavenwood(description)); + return new Statement() { + @Override + public void evaluate() throws Throwable { + Assume.assumeTrue(shouldEnableOnRavenwood(description)); + // Pass through to possible underlying RavenwoodRule for both environment + // configuration and handling method-level annotations + base.evaluate(); + } + }; } - return base; } } diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java index a2e8ec17bbf7..52ea3402fa62 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java @@ -28,6 +28,7 @@ import android.platform.test.annotations.DisabledOnNonRavenwood; import android.platform.test.annotations.DisabledOnRavenwood; import android.platform.test.annotations.EnabledOnRavenwood; import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.util.ArraySet; import org.junit.Assume; import org.junit.rules.TestRule; @@ -277,9 +278,6 @@ public class RavenwoodRule implements TestRule { return false; } } - if (description.getTestClass().getAnnotation(DisabledOnNonRavenwood.class) != null) { - return false; - } return true; } @@ -415,9 +413,10 @@ public class RavenwoodRule implements TestRule { }; } - public static class _$RavenwoodPrivate { - public static boolean isOptionalValidationEnabled() { - return ENABLE_OPTIONAL_VALIDATION; - } + /** + * Do not use it outside ravenwood core classes. + */ + public boolean _ravenwood_private$isOptionalValidationEnabled() { + return ENABLE_OPTIONAL_VALIDATION; } } diff --git a/services/backup/Android.bp b/services/backup/Android.bp index 2a85eb66f6c5..e13746ed13dd 100644 --- a/services/backup/Android.bp +++ b/services/backup/Android.bp @@ -30,5 +30,6 @@ java_library_static { aconfig_declarations { name: "backup_flags", package: "com.android.server.backup", + container: "system", srcs: ["flags.aconfig"], } diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig index 74adfe08dba7..d53f949753d8 100644 --- a/services/backup/flags.aconfig +++ b/services/backup/flags.aconfig @@ -1,4 +1,5 @@ package: "com.android.server.backup" +container: "system" flag { name: "enable_skipping_restore_launched_apps" @@ -58,4 +59,4 @@ flag { description: "Increase BMM logging coverage in restore at install flow." bug: "331749778" is_fixed_read_only: true -}
\ No newline at end of file +} 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 93243fca9dd2..06431fcd783b 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -1068,6 +1068,10 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub @Override public boolean hasCustomAudioInputSupport() throws RemoteException { + return hasCustomAudioInputSupportInternal(); + } + + private boolean hasCustomAudioInputSupportInternal() { if (!Flags.vdmPublicApis()) { return false; } @@ -1119,6 +1123,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub if (mVirtualCameraController != null) { mVirtualCameraController.dump(fout, indent); } + fout.println( + indent + "hasCustomAudioInputSupport: " + hasCustomAudioInputSupportInternal()); } // For display mirroring, we want to dispatch all key events to the source (default) display, diff --git a/services/core/java/com/android/server/am/Android.bp b/services/core/java/com/android/server/am/Android.bp index af1200e4bdf8..0294ffe6e151 100644 --- a/services/core/java/com/android/server/am/Android.bp +++ b/services/core/java/com/android/server/am/Android.bp @@ -1,6 +1,7 @@ aconfig_declarations { name: "am_flags", package: "com.android.server.am", + container: "system", srcs: ["*.aconfig"], } diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS index a656287dc3f9..b517631f35c0 100644 --- a/services/core/java/com/android/server/am/OWNERS +++ b/services/core/java/com/android/server/am/OWNERS @@ -9,7 +9,6 @@ mwachens@google.com sudheersai@google.com suprabh@google.com varunshah@google.com -kwekua@google.com bookatz@google.com jji@google.com diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig index fd847f11157f..e1ccf4d2a362 100644 --- a/services/core/java/com/android/server/am/flags.aconfig +++ b/services/core/java/com/android/server/am/flags.aconfig @@ -1,4 +1,5 @@ package: "com.android.server.am" +container: "system" flag { name: "oomadjuster_correctness_rewrite" diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 656611a79595..77654d4a5413 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -1517,8 +1517,9 @@ public class AudioDeviceBroker { sendLMsgNoDelay(MSG_L_SYNCHRONIZE_ADI_DEVICES_IN_INVENTORY, SENDMSG_QUEUE, deviceState); } - /*package*/ void postUpdatedAdiDeviceState(AdiDeviceState deviceState) { - sendLMsgNoDelay(MSG_L_UPDATED_ADI_DEVICE_STATE, SENDMSG_QUEUE, deviceState); + /*package*/ void postUpdatedAdiDeviceState(AdiDeviceState deviceState, boolean initSA) { + sendILMsgNoDelay( + MSG_IL_UPDATED_ADI_DEVICE_STATE, SENDMSG_QUEUE, initSA ? 1 : 0, deviceState); } /*package*/ static final class CommunicationDeviceInfo { @@ -2048,8 +2049,8 @@ public class AudioDeviceBroker { } } break; - case MSG_L_UPDATED_ADI_DEVICE_STATE: - mAudioService.onUpdatedAdiDeviceState((AdiDeviceState) msg.obj); + case MSG_IL_UPDATED_ADI_DEVICE_STATE: + mAudioService.onUpdatedAdiDeviceState((AdiDeviceState) msg.obj, msg.arg1 == 1); break; default: @@ -2136,7 +2137,7 @@ public class AudioDeviceBroker { private static final int MSG_CHECK_COMMUNICATION_ROUTE_CLIENT_STATE = 56; private static final int MSG_I_UPDATE_LE_AUDIO_GROUP_ADDRESSES = 57; private static final int MSG_L_SYNCHRONIZE_ADI_DEVICES_IN_INVENTORY = 58; - private static final int MSG_L_UPDATED_ADI_DEVICE_STATE = 59; + private static final int MSG_IL_UPDATED_ADI_DEVICE_STATE = 59; diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index ebe9c636d6f6..f38b38154bc3 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -173,7 +173,7 @@ public class AudioDeviceInventory { if (ads.getAudioDeviceCategory() != category && (userDefined || category != AUDIO_DEVICE_CATEGORY_UNKNOWN)) { ads.setAudioDeviceCategory(category); - mDeviceBroker.postUpdatedAdiDeviceState(ads); + mDeviceBroker.postUpdatedAdiDeviceState(ads, false /*initSA*/); mDeviceBroker.postPersistAudioDeviceSettings(); } mDeviceBroker.postSynchronizeAdiDevicesInInventory(ads); @@ -186,7 +186,7 @@ public class AudioDeviceInventory { mDeviceInventory.put(ads.getDeviceId(), ads); checkDeviceInventorySize_l(); - mDeviceBroker.postUpdatedAdiDeviceState(ads); + mDeviceBroker.postUpdatedAdiDeviceState(ads, true /*initSA*/); mDeviceBroker.postPersistAudioDeviceSettings(); } } @@ -216,7 +216,7 @@ public class AudioDeviceInventory { checkDeviceInventorySize_l(); } if (updatedCategory.get()) { - mDeviceBroker.postUpdatedAdiDeviceState(deviceState); + mDeviceBroker.postUpdatedAdiDeviceState(deviceState, false /*initSA*/); } mDeviceBroker.postSynchronizeAdiDevicesInInventory(deviceState); } @@ -318,7 +318,7 @@ public class AudioDeviceInventory { } ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory()); - mDeviceBroker.postUpdatedAdiDeviceState(ads2); + mDeviceBroker.postUpdatedAdiDeviceState(ads2, false /*initSA*/); AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( "synchronizeBleDeviceInInventory synced device pair ads1=" + updatedDevice + " ads2=" + ads2).printLog(TAG)); @@ -339,7 +339,7 @@ public class AudioDeviceInventory { } ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory()); - mDeviceBroker.postUpdatedAdiDeviceState(ads2); + mDeviceBroker.postUpdatedAdiDeviceState(ads2, false /*initSA*/); AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( "synchronizeBleDeviceInInventory synced device pair ads1=" + updatedDevice + " peer ads2=" + ads2).printLog(TAG)); @@ -364,7 +364,7 @@ public class AudioDeviceInventory { } ads.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory()); - mDeviceBroker.postUpdatedAdiDeviceState(ads); + mDeviceBroker.postUpdatedAdiDeviceState(ads, false /*initSA*/); AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( "synchronizeDeviceProfilesInInventory synced device pair ads1=" + updatedDevice + " ads2=" + ads).printLog(TAG)); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 40099581c68b..2a79e316ac2b 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -11284,7 +11284,8 @@ public class AudioService extends IAudioService.Stub mDeviceBroker.addOrUpdateBtAudioDeviceCategoryInInventory(deviceState); mDeviceBroker.postPersistAudioDeviceSettings(); - mSpatializerHelper.refreshDevice(deviceState.getAudioDeviceAttributes()); + mSpatializerHelper.refreshDevice(deviceState.getAudioDeviceAttributes(), + false /* initState */); mSoundDoseHelper.setAudioDeviceCategory(addr, internalType, btAudioDeviceCategory == AUDIO_DEVICE_CATEGORY_HEADPHONES); } @@ -11355,11 +11356,11 @@ public class AudioService extends IAudioService.Stub /** Update the sound dose and spatializer state based on the new AdiDeviceState. */ @VisibleForTesting(visibility = PACKAGE) - public void onUpdatedAdiDeviceState(AdiDeviceState deviceState) { + public void onUpdatedAdiDeviceState(AdiDeviceState deviceState, boolean initSA) { if (deviceState == null) { return; } - mSpatializerHelper.refreshDevice(deviceState.getAudioDeviceAttributes()); + mSpatializerHelper.refreshDevice(deviceState.getAudioDeviceAttributes(), initSA); mSoundDoseHelper.setAudioDeviceCategory(deviceState.getDeviceAddress(), deviceState.getInternalDeviceType(), deviceState.getAudioDeviceCategory() == AUDIO_DEVICE_CATEGORY_HEADPHONES); diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index 3b5fa7f00891..38fa79f7f44a 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -295,11 +295,11 @@ public class SpatializerHelper { // could have been called another time after boot in case of audioserver restart addCompatibleAudioDevice( new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""), - false /*forceEnable*/); + false /*forceEnable*/, false /*forceInit*/); // not force-enabling as this device might already be in the device list addCompatibleAudioDevice( new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, ""), - false /*forceEnable*/); + false /*forceEnable*/, false /*forceInit*/); } catch (RemoteException e) { resetCapabilities(); } finally { @@ -526,7 +526,7 @@ public class SpatializerHelper { } synchronized void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) { - addCompatibleAudioDevice(ada, true /*forceEnable*/); + addCompatibleAudioDevice(ada, true /*forceEnable*/, false /*forceInit*/); } /** @@ -540,7 +540,7 @@ public class SpatializerHelper { */ @GuardedBy("this") private void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada, - boolean forceEnable) { + boolean forceEnable, boolean forceInit) { if (!isDeviceCompatibleWithSpatializationModes(ada)) { return; } @@ -548,6 +548,9 @@ public class SpatializerHelper { final AdiDeviceState deviceState = findSACompatibleDeviceStateForAudioDeviceAttributes(ada); AdiDeviceState updatedDevice = null; // non-null on update. if (deviceState != null) { + if (forceInit) { + initSAState(deviceState); + } if (forceEnable && !deviceState.isSAEnabled()) { updatedDevice = deviceState; updatedDevice.setSAEnabled(true); @@ -756,10 +759,10 @@ public class SpatializerHelper { } } - synchronized void refreshDevice(@NonNull AudioDeviceAttributes ada) { + synchronized void refreshDevice(@NonNull AudioDeviceAttributes ada, boolean initState) { final AdiDeviceState deviceState = findSACompatibleDeviceStateForAudioDeviceAttributes(ada); if (isAvailableForAdiDeviceState(deviceState)) { - addCompatibleAudioDevice(ada, /*forceEnable=*/deviceState.isSAEnabled()); + addCompatibleAudioDevice(ada, /*forceEnable=*/deviceState.isSAEnabled(), initState); setHeadTrackerEnabled(deviceState.isHeadTrackerEnabled(), ada); } else { removeCompatibleAudioDevice(ada); diff --git a/services/core/java/com/android/server/biometrics/Android.bp b/services/core/java/com/android/server/biometrics/Android.bp index 6cbe4adbcdbe..ed5de0305863 100644 --- a/services/core/java/com/android/server/biometrics/Android.bp +++ b/services/core/java/com/android/server/biometrics/Android.bp @@ -1,6 +1,7 @@ aconfig_declarations { name: "biometrics_flags", package: "com.android.server.biometrics", + container: "system", srcs: [ "biometrics.aconfig", ], diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig index b12d831ffe24..7a9491e44cd7 100644 --- a/services/core/java/com/android/server/biometrics/biometrics.aconfig +++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig @@ -1,4 +1,5 @@ package: "com.android.server.biometrics" +container: "system" flag { name: "face_vhal_feature" diff --git a/services/core/java/com/android/server/display/feature/Android.bp b/services/core/java/com/android/server/display/feature/Android.bp index a0ead384c1d2..daf8832fd1d7 100644 --- a/services/core/java/com/android/server/display/feature/Android.bp +++ b/services/core/java/com/android/server/display/feature/Android.bp @@ -1,6 +1,7 @@ aconfig_declarations { name: "display_flags", package: "com.android.server.display.feature.flags", + container: "system", srcs: [ "*.aconfig", ], diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index d4319cc2b633..c68ef9bd4cce 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -1,4 +1,5 @@ package: "com.android.server.display.feature.flags" +container: "system" # Important: Flags must be accessed through DisplayManagerFlags. diff --git a/services/core/java/com/android/server/feature/Android.bp b/services/core/java/com/android/server/feature/Android.bp index 067288d6650d..b0fbab6657b0 100644 --- a/services/core/java/com/android/server/feature/Android.bp +++ b/services/core/java/com/android/server/feature/Android.bp @@ -1,6 +1,7 @@ aconfig_declarations { name: "dropbox_flags", package: "com.android.server.feature.flags", + container: "system", srcs: [ "dropbox_flags.aconfig", ], diff --git a/services/core/java/com/android/server/feature/dropbox_flags.aconfig b/services/core/java/com/android/server/feature/dropbox_flags.aconfig index 14e964b26c6b..98978f03fae5 100644 --- a/services/core/java/com/android/server/feature/dropbox_flags.aconfig +++ b/services/core/java/com/android/server/feature/dropbox_flags.aconfig @@ -1,4 +1,5 @@ package: "com.android.server.feature.flags" +container: "system" flag{ name: "enable_read_dropbox_permission" diff --git a/services/core/java/com/android/server/media/AudioManagerRouteController.java b/services/core/java/com/android/server/media/AudioManagerRouteController.java index e7f717aa6c3b..f27ade42a738 100644 --- a/services/core/java/com/android/server/media/AudioManagerRouteController.java +++ b/services/core/java/com/android/server/media/AudioManagerRouteController.java @@ -136,7 +136,7 @@ import java.util.Objects; mBluetoothRouteController = new BluetoothDeviceRoutesManager( - mContext, btAdapter, this::rebuildAvailableRoutesAndNotify); + mContext, mHandler, btAdapter, this::rebuildAvailableRoutesAndNotify); // Just build routes but don't notify. The caller may not expect the listener to be invoked // before this constructor has finished executing. rebuildAvailableRoutes(); @@ -204,23 +204,24 @@ import java.util.Objects; Slog.w(TAG, "transferTo: Ignoring transfer request to unknown route id : " + routeId); return; } - // TODO: b/329929065 - Push audio manager and bluetooth operations to the handler, so that - // they don't run on a binder thread, so as to prevent possible deadlocks (these operations - // may need system_server binder threads to complete). - if (mediaRoute2InfoHolder.mCorrespondsToInactiveBluetoothRoute) { - // By default, the last connected device is the active route so we don't need to apply a - // routing audio policy. - mBluetoothRouteController.activateBluetoothDeviceWithAddress( - mediaRoute2InfoHolder.mMediaRoute2Info.getAddress()); - mAudioManager.removePreferredDeviceForStrategy(mStrategyForMedia); - } else { - AudioDeviceAttributes attr = - new AudioDeviceAttributes( - AudioDeviceAttributes.ROLE_OUTPUT, - mediaRoute2InfoHolder.mAudioDeviceInfoType, - /* address= */ ""); // This is not a BT device, hence no address needed. - mAudioManager.setPreferredDeviceForStrategy(mStrategyForMedia, attr); - } + Runnable transferAction = getTransferActionForRoute(mediaRoute2InfoHolder); + Runnable guardedTransferAction = + () -> { + try { + transferAction.run(); + } catch (Throwable throwable) { + // We swallow the exception to avoid crashing system_server, since this + // doesn't run on a binder thread. + Slog.e( + TAG, + "Unexpected exception while transferring to route id: " + routeId, + throwable); + mHandler.post(this::rebuildAvailableRoutesAndNotify); + } + }; + // We post the transfer operation to the handler to avoid making these calls on a binder + // thread. See class javadoc for details. + mHandler.post(guardedTransferAction); } @RequiresPermission( @@ -236,6 +237,28 @@ import java.util.Objects; return true; } + private Runnable getTransferActionForRoute(MediaRoute2InfoHolder mediaRoute2InfoHolder) { + if (mediaRoute2InfoHolder.mCorrespondsToInactiveBluetoothRoute) { + String deviceAddress = mediaRoute2InfoHolder.mMediaRoute2Info.getAddress(); + return () -> { + // By default, the last connected device is the active route so we don't + // need to apply a routing audio policy. + mBluetoothRouteController.activateBluetoothDeviceWithAddress(deviceAddress); + mAudioManager.removePreferredDeviceForStrategy(mStrategyForMedia); + }; + + } else { + AudioDeviceAttributes deviceAttributes = + new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + mediaRoute2InfoHolder.mAudioDeviceInfoType, + /* address= */ ""); // This is not a BT device, hence no address needed. + return () -> + mAudioManager.setPreferredDeviceForStrategy( + mStrategyForMedia, deviceAttributes); + } + } + @RequiresPermission( anyOf = { Manifest.permission.MODIFY_AUDIO_ROUTING, diff --git a/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java b/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java index b881ef6195d5..8b65ea305ad8 100644 --- a/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java +++ b/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java @@ -31,6 +31,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.media.MediaRoute2Info; +import android.os.Handler; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; @@ -77,26 +78,35 @@ import java.util.stream.Collectors; @NonNull private final Context mContext; - @NonNull - private final BluetoothAdapter mBluetoothAdapter; + @NonNull private final Handler mHandler; + @NonNull private final BluetoothAdapter mBluetoothAdapter; @NonNull private final BluetoothRouteController.BluetoothRoutesUpdatedListener mListener; @NonNull private final BluetoothProfileMonitor mBluetoothProfileMonitor; - BluetoothDeviceRoutesManager(@NonNull Context context, + BluetoothDeviceRoutesManager( + @NonNull Context context, + @NonNull Handler handler, @NonNull BluetoothAdapter bluetoothAdapter, @NonNull BluetoothRouteController.BluetoothRoutesUpdatedListener listener) { - this(context, bluetoothAdapter, - new BluetoothProfileMonitor(context, bluetoothAdapter), listener); + this( + context, + handler, + bluetoothAdapter, + new BluetoothProfileMonitor(context, bluetoothAdapter), + listener); } @VisibleForTesting - BluetoothDeviceRoutesManager(@NonNull Context context, + BluetoothDeviceRoutesManager( + @NonNull Context context, + @NonNull Handler handler, @NonNull BluetoothAdapter bluetoothAdapter, @NonNull BluetoothProfileMonitor bluetoothProfileMonitor, @NonNull BluetoothRouteController.BluetoothRoutesUpdatedListener listener) { mContext = Objects.requireNonNull(context); + mHandler = handler; mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter); mBluetoothProfileMonitor = Objects.requireNonNull(bluetoothProfileMonitor); mListener = Objects.requireNonNull(listener); @@ -298,6 +308,26 @@ import java.util.stream.Collectors; }; } + private void handleBluetoothAdapterStateChange(int state) { + if (state == BluetoothAdapter.STATE_OFF || state == BluetoothAdapter.STATE_TURNING_OFF) { + synchronized (BluetoothDeviceRoutesManager.this) { + mBluetoothRoutes.clear(); + } + notifyBluetoothRoutesUpdated(); + } else if (state == BluetoothAdapter.STATE_ON) { + updateBluetoothRoutes(); + + boolean shouldCallListener; + synchronized (BluetoothDeviceRoutesManager.this) { + shouldCallListener = !mBluetoothRoutes.isEmpty(); + } + + if (shouldCallListener) { + notifyBluetoothRoutesUpdated(); + } + } + } + private static class BluetoothRouteInfo { private BluetoothDevice mBtDevice; private MediaRoute2Info mRoute; @@ -308,23 +338,10 @@ import java.util.stream.Collectors; @Override public void onReceive(Context context, Intent intent) { int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); - if (state == BluetoothAdapter.STATE_OFF - || state == BluetoothAdapter.STATE_TURNING_OFF) { - synchronized (BluetoothDeviceRoutesManager.this) { - mBluetoothRoutes.clear(); - } - notifyBluetoothRoutesUpdated(); - } else if (state == BluetoothAdapter.STATE_ON) { - updateBluetoothRoutes(); - - boolean shouldCallListener; - synchronized (BluetoothDeviceRoutesManager.this) { - shouldCallListener = !mBluetoothRoutes.isEmpty(); - } - - if (shouldCallListener) { - notifyBluetoothRoutesUpdated(); - } + if (Flags.enableMr2ServiceNonMainBgThread()) { + mHandler.post(() -> handleBluetoothAdapterStateChange(state)); + } else { + handleBluetoothAdapterStateChange(state); } } } @@ -337,8 +354,16 @@ import java.util.stream.Collectors; case BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED: case BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED: case BluetoothDevice.ACTION_ALIAS_CHANGED: - updateBluetoothRoutes(); - notifyBluetoothRoutesUpdated(); + if (Flags.enableMr2ServiceNonMainBgThread()) { + mHandler.post( + () -> { + updateBluetoothRoutes(); + notifyBluetoothRoutesUpdated(); + }); + } else { + updateBluetoothRoutes(); + notifyBluetoothRoutesUpdated(); + } } } } diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index aa71e054ddee..e50189b2e79d 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -189,12 +189,10 @@ class MediaRouter2ServiceImpl { mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); mAppOpsManager = mContext.getSystemService(AppOpsManager.class); - if (!Flags.disableScreenOffBroadcastReceiver()) { - IntentFilter screenOnOffIntentFilter = new IntentFilter(); - screenOnOffIntentFilter.addAction(ACTION_SCREEN_ON); - screenOnOffIntentFilter.addAction(ACTION_SCREEN_OFF); - mContext.registerReceiver(mScreenOnOffReceiver, screenOnOffIntentFilter); - } + IntentFilter screenOnOffIntentFilter = new IntentFilter(); + screenOnOffIntentFilter.addAction(ACTION_SCREEN_ON); + screenOnOffIntentFilter.addAction(ACTION_SCREEN_OFF); + mContext.registerReceiver(mScreenOnOffReceiver, screenOnOffIntentFilter); // Passing null package name to listen to all events. mAppOpsManager.startWatchingMode( @@ -3435,9 +3433,7 @@ class MediaRouter2ServiceImpl { @NonNull private static List<RouterRecord> getIndividuallyActiveRouters( MediaRouter2ServiceImpl service, List<RouterRecord> allRouterRecords) { - if (!Flags.disableScreenOffBroadcastReceiver() - && !service.mPowerManager.isInteractive() - && !Flags.enableScreenOffScanning()) { + if (!service.mPowerManager.isInteractive() && !Flags.enableScreenOffScanning()) { return Collections.emptyList(); } @@ -3453,9 +3449,7 @@ class MediaRouter2ServiceImpl { private static boolean areManagersScanning( MediaRouter2ServiceImpl service, List<ManagerRecord> managerRecords) { - if (!Flags.disableScreenOffBroadcastReceiver() - && !service.mPowerManager.isInteractive() - && !Flags.enableScreenOffScanning()) { + if (!service.mPowerManager.isInteractive() && !Flags.enableScreenOffScanning()) { return false; } diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index 4bdca29d3bd0..1a129cb080a8 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -53,6 +53,7 @@ import android.media.RoutingSessionInfo; import android.os.Binder; import android.os.Bundle; import android.os.Handler; +import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; @@ -70,6 +71,7 @@ import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.DumpUtils; +import com.android.media.flags.Flags; import com.android.server.LocalServices; import com.android.server.Watchdog; import com.android.server.pm.UserManagerInternal; @@ -94,6 +96,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub implements Watchdog.Monitor { private static final String TAG = "MediaRouterService"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private static final String WORKER_THREAD_NAME = "MediaRouterServiceThread"; /** * Timeout in milliseconds for a selected route to transition from a disconnected state to a @@ -126,7 +129,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub private final IAudioService mAudioService; private final AudioPlayerStateMonitor mAudioPlayerStateMonitor; - private final Handler mHandler = new Handler(); + private final Handler mHandler; private final IntArray mActivePlayerMinPriorityQueue = new IntArray(); private final IntArray mActivePlayerUidMinPriorityQueue = new IntArray(); @@ -142,7 +145,14 @@ public final class MediaRouterService extends IMediaRouterService.Stub @RequiresPermission(Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS) public MediaRouterService(Context context) { - mLooper = Looper.getMainLooper(); + if (Flags.enableMr2ServiceNonMainBgThread()) { + HandlerThread handlerThread = new HandlerThread(WORKER_THREAD_NAME); + handlerThread.start(); + mLooper = handlerThread.getLooper(); + } else { + mLooper = Looper.myLooper(); + } + mHandler = new Handler(mLooper); mService2 = new MediaRouter2ServiceImpl(context, mLooper); mContext = context; Watchdog.getInstance().addMonitor(this); diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 802acba2e412..8df38a8d565a 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -653,9 +653,11 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { return; } - // TODO: b/310145678 - Post this to mHandler once mHandler does not run on the main - // thread. - updateVolume(); + if (Flags.enableMr2ServiceNonMainBgThread()) { + mHandler.post(SystemMediaRoute2Provider.this::updateVolume); + } else { + updateVolume(); + } } } } diff --git a/services/core/java/com/android/server/net/Android.bp b/services/core/java/com/android/server/net/Android.bp index 71d8e6ba367e..3ac2d232dfc8 100644 --- a/services/core/java/com/android/server/net/Android.bp +++ b/services/core/java/com/android/server/net/Android.bp @@ -1,6 +1,7 @@ aconfig_declarations { name: "net_flags", package: "com.android.server.net", + container: "system", srcs: ["*.aconfig"], } diff --git a/services/core/java/com/android/server/net/flags.aconfig b/services/core/java/com/android/server/net/flags.aconfig index 419665a0a5ab..d9491de52d87 100644 --- a/services/core/java/com/android/server/net/flags.aconfig +++ b/services/core/java/com/android/server/net/flags.aconfig @@ -1,4 +1,5 @@ package: "com.android.server.net" +container: "system" flag { name: "network_blocked_for_top_sleeping_and_above" diff --git a/services/core/java/com/android/server/notification/Android.bp b/services/core/java/com/android/server/notification/Android.bp index 9be43581aed5..d757470d86e9 100644 --- a/services/core/java/com/android/server/notification/Android.bp +++ b/services/core/java/com/android/server/notification/Android.bp @@ -10,6 +10,7 @@ java_aconfig_library { aconfig_declarations { name: "notification_flags", package: "com.android.server.notification", + container: "system", srcs: [ "flags.aconfig", ], diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig index d4641c45b8a6..077ed5a72ae9 100644 --- a/services/core/java/com/android/server/notification/flags.aconfig +++ b/services/core/java/com/android/server/notification/flags.aconfig @@ -1,4 +1,5 @@ package: "com.android.server.notification" +container: "system" flag { name: "expire_bitmaps" diff --git a/services/core/java/com/android/server/os/Android.bp b/services/core/java/com/android/server/os/Android.bp index 565dc3b644ea..c5886708ad85 100644 --- a/services/core/java/com/android/server/os/Android.bp +++ b/services/core/java/com/android/server/os/Android.bp @@ -1,6 +1,7 @@ aconfig_declarations { name: "core_os_flags", package: "com.android.server.os", + container: "system", srcs: [ "*.aconfig", ], diff --git a/services/core/java/com/android/server/os/core_os_flags.aconfig b/services/core/java/com/android/server/os/core_os_flags.aconfig index cbc0d54046df..ae33df83e3aa 100644 --- a/services/core/java/com/android/server/os/core_os_flags.aconfig +++ b/services/core/java/com/android/server/os/core_os_flags.aconfig @@ -1,4 +1,5 @@ package: "com.android.server.os" +container: "system" flag { name: "proto_tombstone" diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java index 017cf55541fc..306f77d3d11e 100644 --- a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java +++ b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java @@ -25,6 +25,7 @@ import static com.android.server.pm.CrossProfileIntentFilter.FLAG_IS_PACKAGE_FOR import android.content.Intent; import android.hardware.usb.UsbManager; +import android.nfc.NfcAdapter; import android.provider.AlarmClock; import android.provider.MediaStore; @@ -361,6 +362,7 @@ public class DefaultCrossProfileIntentFiltersUtils { .addCategory(Intent.CATEGORY_DEFAULT) .build(); + public static List<DefaultCrossProfileIntentFilter> getDefaultManagedProfileFilters() { List<DefaultCrossProfileIntentFilter> filters = new ArrayList<DefaultCrossProfileIntentFilter>(); @@ -637,6 +639,33 @@ public class DefaultCrossProfileIntentFiltersUtils { .addDataType("video/*") .build(); + /** NFC TAG intent is always resolved by the primary user. */ + static final DefaultCrossProfileIntentFilter PARENT_TO_CLONE_NFC_TAG_DISCOVERED = + new DefaultCrossProfileIntentFilter.Builder( + DefaultCrossProfileIntentFilter.Direction.TO_PROFILE, + /* flags= */0, + /* letsPersonalDataIntoProfile= */ false) + .addAction(NfcAdapter.ACTION_TAG_DISCOVERED) + .build(); + + /** NFC TAG intent is always resolved by the primary user. */ + static final DefaultCrossProfileIntentFilter PARENT_TO_CLONE_NFC_TECH_DISCOVERED = + new DefaultCrossProfileIntentFilter.Builder( + DefaultCrossProfileIntentFilter.Direction.TO_PROFILE, + /* flags= */0, + /* letsPersonalDataIntoProfile= */ false) + .addAction(NfcAdapter.ACTION_TECH_DISCOVERED) + .build(); + + /** NFC TAG intent is always resolved by the primary user. */ + static final DefaultCrossProfileIntentFilter PARENT_TO_CLONE_NFC_NDEF_DISCOVERED = + new DefaultCrossProfileIntentFilter.Builder( + DefaultCrossProfileIntentFilter.Direction.TO_PROFILE, + /* flags= */0, + /* letsPersonalDataIntoProfile= */ false) + .addAction(NfcAdapter.ACTION_NDEF_DISCOVERED) + .build(); + public static List<DefaultCrossProfileIntentFilter> getDefaultCloneProfileFilters() { return Arrays.asList( PARENT_TO_CLONE_SEND_ACTION, @@ -652,7 +681,10 @@ public class DefaultCrossProfileIntentFiltersUtils { CLONE_TO_PARENT_SMS_MMS, CLONE_TO_PARENT_PHOTOPICKER_SELECTION, CLONE_TO_PARENT_ACTION_PICK_IMAGES, - CLONE_TO_PARENT_ACTION_PICK_IMAGES_WITH_DATA_TYPES + CLONE_TO_PARENT_ACTION_PICK_IMAGES_WITH_DATA_TYPES, + PARENT_TO_CLONE_NFC_TAG_DISCOVERED, + PARENT_TO_CLONE_NFC_TECH_DISCOVERED, + PARENT_TO_CLONE_NFC_NDEF_DISCOVERED ); } diff --git a/services/core/java/com/android/server/policy/Android.bp b/services/core/java/com/android/server/policy/Android.bp index fa55bf0a30e5..325b6cbf5f2c 100644 --- a/services/core/java/com/android/server/policy/Android.bp +++ b/services/core/java/com/android/server/policy/Android.bp @@ -1,6 +1,7 @@ aconfig_declarations { name: "policy_flags", package: "com.android.server.policy", + container: "system", srcs: ["*.aconfig"], } diff --git a/services/core/java/com/android/server/policy/window_policy_flags.aconfig b/services/core/java/com/android/server/policy/window_policy_flags.aconfig index 2154a26acbaf..7914b949ad5f 100644 --- a/services/core/java/com/android/server/policy/window_policy_flags.aconfig +++ b/services/core/java/com/android/server/policy/window_policy_flags.aconfig @@ -1,4 +1,5 @@ package: "com.android.server.policy" +container: "system" flag { name: "support_input_wakeup_delegate" diff --git a/services/core/java/com/android/server/power/Android.bp b/services/core/java/com/android/server/power/Android.bp index 863ff76cb0c7..5d4065df91d6 100644 --- a/services/core/java/com/android/server/power/Android.bp +++ b/services/core/java/com/android/server/power/Android.bp @@ -1,6 +1,7 @@ aconfig_declarations { name: "backstage_power_flags", package: "com.android.server.power.optimization", + container: "system", srcs: [ "stats/*.aconfig", ], diff --git a/services/core/java/com/android/server/power/batterysaver/OWNERS b/services/core/java/com/android/server/power/batterysaver/OWNERS index cf23bea97289..dc2d0b320d58 100644 --- a/services/core/java/com/android/server/power/batterysaver/OWNERS +++ b/services/core/java/com/android/server/power/batterysaver/OWNERS @@ -1,3 +1,2 @@ -kwekua@google.com omakoto@google.com -yamasani@google.com
\ No newline at end of file +yamasani@google.com diff --git a/services/core/java/com/android/server/power/feature/Android.bp b/services/core/java/com/android/server/power/feature/Android.bp index 2295b41009de..fee3114015a6 100644 --- a/services/core/java/com/android/server/power/feature/Android.bp +++ b/services/core/java/com/android/server/power/feature/Android.bp @@ -1,6 +1,7 @@ aconfig_declarations { name: "power_flags", package: "com.android.server.power.feature.flags", + container: "system", srcs: [ "*.aconfig", ], diff --git a/services/core/java/com/android/server/power/feature/power_flags.aconfig b/services/core/java/com/android/server/power/feature/power_flags.aconfig index f5dfb5cc3afe..ca58153cf25b 100644 --- a/services/core/java/com/android/server/power/feature/power_flags.aconfig +++ b/services/core/java/com/android/server/power/feature/power_flags.aconfig @@ -1,4 +1,5 @@ package: "com.android.server.power.feature.flags" +container: "system" # Important: Flags must be accessed through PowerManagerFlags. diff --git a/services/core/java/com/android/server/power/hint/Android.bp b/services/core/java/com/android/server/power/hint/Android.bp index 8a98de673c3d..d7dd902dbabc 100644 --- a/services/core/java/com/android/server/power/hint/Android.bp +++ b/services/core/java/com/android/server/power/hint/Android.bp @@ -1,6 +1,7 @@ aconfig_declarations { name: "power_hint_flags", package: "com.android.server.power.hint", + container: "system", srcs: [ "flags.aconfig", ], diff --git a/services/core/java/com/android/server/power/hint/flags.aconfig b/services/core/java/com/android/server/power/hint/flags.aconfig index f4afcb141b19..099774420d23 100644 --- a/services/core/java/com/android/server/power/hint/flags.aconfig +++ b/services/core/java/com/android/server/power/hint/flags.aconfig @@ -1,4 +1,5 @@ package: "com.android.server.power.hint" +container: "system" flag { name: "powerhint_thread_cleanup" diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig index c42cceab55be..54ae84f4535b 100644 --- a/services/core/java/com/android/server/power/stats/flags.aconfig +++ b/services/core/java/com/android/server/power/stats/flags.aconfig @@ -1,4 +1,5 @@ package: "com.android.server.power.optimization" +container: "system" flag { name: "power_monitor_api" diff --git a/services/core/java/com/android/server/stats/Android.bp b/services/core/java/com/android/server/stats/Android.bp index e597c3a6b6e6..f7955e86660a 100644 --- a/services/core/java/com/android/server/stats/Android.bp +++ b/services/core/java/com/android/server/stats/Android.bp @@ -1,6 +1,7 @@ aconfig_declarations { name: "stats_flags", package: "com.android.server.stats", + container: "system", srcs: [ "stats_flags.aconfig", ], diff --git a/services/core/java/com/android/server/stats/stats_flags.aconfig b/services/core/java/com/android/server/stats/stats_flags.aconfig index 5101a6982fe1..101b98e1785d 100644 --- a/services/core/java/com/android/server/stats/stats_flags.aconfig +++ b/services/core/java/com/android/server/stats/stats_flags.aconfig @@ -1,4 +1,5 @@ package: "com.android.server.stats" +container: "system" flag { name: "add_mobile_bytes_transfer_by_proc_state_puller" diff --git a/services/core/java/com/android/server/utils/Android.bp b/services/core/java/com/android/server/utils/Android.bp index 3a334bee93ff..ffb9aec1cbef 100644 --- a/services/core/java/com/android/server/utils/Android.bp +++ b/services/core/java/com/android/server/utils/Android.bp @@ -1,6 +1,7 @@ aconfig_declarations { name: "com.android.server.utils-aconfig", package: "com.android.server.utils", + container: "system", srcs: ["*.aconfig"], } diff --git a/services/core/java/com/android/server/utils/flags.aconfig b/services/core/java/com/android/server/utils/flags.aconfig index 163116b9c5c7..6f37837d4f3b 100644 --- a/services/core/java/com/android/server/utils/flags.aconfig +++ b/services/core/java/com/android/server/utils/flags.aconfig @@ -1,4 +1,5 @@ package: "com.android.server.utils" +container: "system" flag { name: "anr_timer_service" diff --git a/services/core/java/com/android/server/utils/quota/OWNERS b/services/core/java/com/android/server/utils/quota/OWNERS index a2943f39db24..469acb23f0b9 100644 --- a/services/core/java/com/android/server/utils/quota/OWNERS +++ b/services/core/java/com/android/server/utils/quota/OWNERS @@ -1,4 +1,3 @@ dplotnikov@google.com -kwekua@google.com omakoto@google.com yamasani@google.com diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp index 6ad8d790485c..6393e11b7432 100644 --- a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp @@ -1,6 +1,7 @@ aconfig_declarations { name: "device_state_flags", package: "com.android.server.policy.feature.flags", + container: "system", srcs: [ "device_state_flags.aconfig", ], diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig index 29e258cc90ff..21e33dd1b99a 100644 --- a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig @@ -1,4 +1,5 @@ package: "com.android.server.policy.feature.flags" +container: "system" flag { name: "enable_dual_display_blocking" diff --git a/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java index a918be2292af..8bdfc500fdc2 100644 --- a/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java +++ b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java @@ -69,6 +69,13 @@ import java.util.Set; public class AudioManagerRouteControllerTest { private static final String FAKE_ROUTE_NAME = "fake name"; + + /** + * The number of milliseconds to wait for an asynchronous operation before failing an associated + * assertion. + */ + private static final int ASYNC_CALL_TIMEOUTS_MS = 1000; + private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER = createAudioDeviceInfo( AudioSystem.DEVICE_OUT_SPEAKER, "name_builtin", /* address= */ null); @@ -231,7 +238,7 @@ public class AudioManagerRouteControllerTest { MediaRoute2Info builtInSpeakerRoute = getAvailableRouteWithType(MediaRoute2Info.TYPE_BUILTIN_SPEAKER); mControllerUnderTest.transferTo(builtInSpeakerRoute.getId()); - verify(mMockAudioManager) + verify(mMockAudioManager, Mockito.timeout(ASYNC_CALL_TIMEOUTS_MS)) .setPreferredDeviceForStrategy( mMediaAudioProductStrategy, createAudioDeviceAttribute(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER)); @@ -239,7 +246,7 @@ public class AudioManagerRouteControllerTest { MediaRoute2Info wiredHeadsetRoute = getAvailableRouteWithType(MediaRoute2Info.TYPE_WIRED_HEADSET); mControllerUnderTest.transferTo(wiredHeadsetRoute.getId()); - verify(mMockAudioManager) + verify(mMockAudioManager, Mockito.timeout(ASYNC_CALL_TIMEOUTS_MS)) .setPreferredDeviceForStrategy( mMediaAudioProductStrategy, createAudioDeviceAttribute(AudioDeviceInfo.TYPE_WIRED_HEADSET)); diff --git a/services/tests/mockingservicestests/src/com/android/server/OWNERS b/services/tests/mockingservicestests/src/com/android/server/OWNERS index f80156021408..0eb8639bd005 100644 --- a/services/tests/mockingservicestests/src/com/android/server/OWNERS +++ b/services/tests/mockingservicestests/src/com/android/server/OWNERS @@ -1,5 +1,5 @@ per-file *Alarm* = file:/apex/jobscheduler/OWNERS per-file *AppStateTracker* = file:/apex/jobscheduler/OWNERS per-file *DeviceIdleController* = file:/apex/jobscheduler/OWNERS -per-file SensitiveContentProtectionManagerServiceTest.java = file:/core/java/android/permission/OWNERS +per-file SensitiveContentProtectionManagerService* = file:/core/java/android/permission/OWNERS per-file RescuePartyTest.java = file:/packages/CrashRecovery/OWNERS diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java index 682569f1d9ab..697548cbe2b4 100644 --- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java @@ -1111,16 +1111,9 @@ public class RescuePartyTest { // mock properties in BootThreshold try { - if (Flags.recoverabilityDetection()) { - mSpyBootThreshold = spy(watchdog.new BootThreshold( - PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT, - PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS, - PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT)); - } else { - mSpyBootThreshold = spy(watchdog.new BootThreshold( + mSpyBootThreshold = spy(watchdog.new BootThreshold( PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT, PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS)); - } mCrashRecoveryPropertiesMap = new HashMap<>(); doAnswer((Answer<Integer>) invocationOnMock -> { diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java index 506514469338..edee8cd217d0 100644 --- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java @@ -25,7 +25,6 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; @@ -34,6 +33,7 @@ import static org.mockito.Mockito.when; import android.content.pm.PackageManagerInternal; import android.media.projection.MediaProjectionInfo; import android.media.projection.MediaProjectionManager; +import android.os.Process; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; @@ -79,7 +79,8 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { private static final String NOTIFICATION_PKG_1 = "com.android.server.notification.one"; private static final String NOTIFICATION_PKG_2 = "com.android.server.notification.two"; - private static final String EXEMPTED_SCREEN_RECORDER_PACKAGE = "test.screen.recorder.package"; + private static final String SCREEN_RECORDER_PACKAGE = "test.screen.recorder.package"; + private static final String EXEMPTED_SCREEN_RECORDER_PACKAGE = "exempt.screen.recorder.package"; private static final int NOTIFICATION_UID_1 = 5; private static final int NOTIFICATION_UID_2 = 6; @@ -281,10 +282,18 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { .getActiveNotifications(); } + private MediaProjectionInfo createMediaProjectionInfo() { + return new MediaProjectionInfo(SCREEN_RECORDER_PACKAGE, Process.myUserHandle(), null); + } + + private MediaProjectionInfo createExemptMediaProjectionInfo() { + return new MediaProjectionInfo( + EXEMPTED_SCREEN_RECORDER_PACKAGE, Process.myUserHandle(), null); + } + @Test public void mediaProjectionOnStart_verifyExemptedRecorderPackage() { - MediaProjectionInfo mediaProjectionInfo = mock(MediaProjectionInfo.class); - when(mediaProjectionInfo.getPackageName()).thenReturn(EXEMPTED_SCREEN_RECORDER_PACKAGE); + MediaProjectionInfo mediaProjectionInfo = createExemptMediaProjectionInfo(); mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo); @@ -295,7 +304,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { public void mediaProjectionOnStart_onProjectionStart_setWmBlockedPackages() { ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages); } @@ -304,18 +313,18 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { public void mediaProjectionOnStart_noSensitiveNotifications_noBlockedPackages() { setupNoSensitiveNotifications(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); - verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); + verifyZeroInteractions(mWindowManager); } @Test public void mediaProjectionOnStart_noNotifications_noBlockedPackages() { setupNoNotifications(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); - verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); + verifyZeroInteractions(mWindowManager); } @Test @@ -323,7 +332,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { ArraySet<PackageInfo> expectedBlockedPackages = setupMultipleSensitiveNotificationsFromSamePackageAndUid(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages); } @@ -333,7 +342,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { ArraySet<PackageInfo> expectedBlockedPackages = setupMultipleSensitiveNotificationsFromDifferentPackage(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages); } @@ -343,7 +352,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { ArraySet<PackageInfo> expectedBlockedPackages = setupMultipleSensitiveNotificationsFromDifferentUid(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages); } @@ -352,7 +361,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { public void mediaProjectionOnStop_onProjectionEnd_clearWmBlockedPackages() { setupSensitiveNotification(); - MediaProjectionInfo mediaProjectionInfo = mock(MediaProjectionInfo.class); + MediaProjectionInfo mediaProjectionInfo = createMediaProjectionInfo(); mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo); Mockito.reset(mWindowManager); @@ -365,7 +374,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { public void mediaProjectionOnStart_afterOnStop_onProjectionStart_setWmBlockedPackages() { ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification(); - MediaProjectionInfo mediaProjectionInfo = mock(MediaProjectionInfo.class); + MediaProjectionInfo mediaProjectionInfo = createMediaProjectionInfo(); mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo); mMediaProjectionCallbackCaptor.getValue().onStop(mediaProjectionInfo); Mockito.reset(mWindowManager); @@ -381,9 +390,9 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { .when(mSensitiveContentProtectionManagerService.mNotificationListener) .getActiveNotifications(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); - verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); + verifyZeroInteractions(mWindowManager); } @Test @@ -392,9 +401,9 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { .when(mSensitiveContentProtectionManagerService.mNotificationListener) .getCurrentRanking(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); - verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); + verifyZeroInteractions(mWindowManager); } @Test @@ -403,9 +412,9 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { .when(mSensitiveContentProtectionManagerService.mNotificationListener) .getCurrentRanking(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); - verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); + verifyZeroInteractions(mWindowManager); } @Test @@ -416,9 +425,9 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { .when(mSensitiveContentProtectionManagerService.mNotificationListener) .getCurrentRanking(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); - verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); + verifyZeroInteractions(mWindowManager); } @Test @@ -426,7 +435,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mockDisabledViaDevelopOption(); setupSensitiveNotification(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); verifyZeroInteractions(mWindowManager); } @@ -447,8 +456,9 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 // as non-sensitive setupSensitiveNotification(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); - mMediaProjectionCallbackCaptor.getValue().onStop(mock(MediaProjectionInfo.class)); + MediaProjectionInfo mediaProjectionInfo = createMediaProjectionInfo(); + mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo); + mMediaProjectionCallbackCaptor.getValue().onStop(mediaProjectionInfo); Mockito.reset(mWindowManager); mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected(); @@ -461,7 +471,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 // as non-sensitive ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); Mockito.reset(mWindowManager); mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected(); @@ -472,23 +482,23 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { @Test public void nlsOnListenerConnected_noSensitiveNotifications_noBlockedPackages() { setupNoSensitiveNotifications(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); Mockito.reset(mWindowManager); mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected(); - verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); + verifyZeroInteractions(mWindowManager); } @Test public void nlsOnListenerConnected_noNotifications_noBlockedPackages() { setupNoNotifications(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); Mockito.reset(mWindowManager); mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected(); - verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); + verifyZeroInteractions(mWindowManager); } @Test @@ -496,7 +506,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 // as non-sensitive setupSensitiveNotification(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); Mockito.reset(mWindowManager); doReturn(null) .when(mSensitiveContentProtectionManagerService.mNotificationListener) @@ -504,7 +514,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected(); - verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); + verifyZeroInteractions(mWindowManager); } @Test @@ -512,7 +522,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 // as non-sensitive setupSensitiveNotification(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); Mockito.reset(mWindowManager); when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1))).thenReturn(null); doReturn(mRankingMap) @@ -521,7 +531,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected(); - verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); + verifyZeroInteractions(mWindowManager); } @Test @@ -530,7 +540,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 // as non-sensitive setupSensitiveNotification(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected(); verifyZeroInteractions(mWindowManager); @@ -553,8 +563,9 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 // as non-sensitive setupSensitiveNotification(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); - mMediaProjectionCallbackCaptor.getValue().onStop(mock(MediaProjectionInfo.class)); + MediaProjectionInfo mediaProjectionInfo = createMediaProjectionInfo(); + mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo); + mMediaProjectionCallbackCaptor.getValue().onStop(mediaProjectionInfo); Mockito.reset(mWindowManager); mSensitiveContentProtectionManagerService.mNotificationListener @@ -568,7 +579,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 // as non-sensitive ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); Mockito.reset(mWindowManager); mSensitiveContentProtectionManagerService.mNotificationListener @@ -580,25 +591,25 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { @Test public void nlsOnNotificationRankingUpdate_noSensitiveNotifications_noBlockedPackages() { setupNoSensitiveNotifications(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); Mockito.reset(mWindowManager); mSensitiveContentProtectionManagerService.mNotificationListener .onNotificationRankingUpdate(mRankingMap); - verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); + verifyZeroInteractions(mWindowManager); } @Test public void nlsOnNotificationRankingUpdate_noNotifications_noBlockedPackages() { setupNoNotifications(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); Mockito.reset(mWindowManager); mSensitiveContentProtectionManagerService.mNotificationListener .onNotificationRankingUpdate(mRankingMap); - verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); + verifyZeroInteractions(mWindowManager); } @Test @@ -606,13 +617,13 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 // as non-sensitive setupSensitiveNotification(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); Mockito.reset(mWindowManager); mSensitiveContentProtectionManagerService.mNotificationListener .onNotificationRankingUpdate(null); - verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); + verifyZeroInteractions(mWindowManager); } @Test @@ -620,7 +631,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 // as non-sensitive setupSensitiveNotification(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); Mockito.reset(mWindowManager); when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1))).thenReturn(null); doReturn(mRankingMap) @@ -630,7 +641,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener .onNotificationRankingUpdate(mRankingMap); - verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); + verifyZeroInteractions(mWindowManager); } @Test @@ -638,7 +649,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 // as non-sensitive setupSensitiveNotification(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); Mockito.reset(mWindowManager); doThrow(SecurityException.class) @@ -648,7 +659,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener .onNotificationRankingUpdate(mRankingMap); - verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); + verifyZeroInteractions(mWindowManager); } @Test @@ -657,7 +668,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 // as non-sensitive setupSensitiveNotification(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); mSensitiveContentProtectionManagerService.mNotificationListener .onNotificationRankingUpdate(mRankingMap); @@ -681,8 +692,9 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 // as non-sensitive setupSensitiveNotification(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); - mMediaProjectionCallbackCaptor.getValue().onStop(mock(MediaProjectionInfo.class)); + MediaProjectionInfo mediaProjectionInfo = createMediaProjectionInfo(); + mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo); + mMediaProjectionCallbackCaptor.getValue().onStop(mediaProjectionInfo); Mockito.reset(mWindowManager); mSensitiveContentProtectionManagerService.mNotificationListener @@ -696,7 +708,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 // as non-sensitive setupSensitiveNotification(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); Mockito.reset(mWindowManager); mSensitiveContentProtectionManagerService.mNotificationListener @@ -712,7 +724,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 // as non-sensitive setupSensitiveNotification(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); Mockito.reset(mWindowManager); mSensitiveContentProtectionManagerService.mNotificationListener @@ -726,7 +738,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 // as non-sensitive setupSensitiveNotification(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); Mockito.reset(mWindowManager); mSensitiveContentProtectionManagerService.mNotificationListener @@ -740,7 +752,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 // as non-sensitive setupSensitiveNotification(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); Mockito.reset(mWindowManager); mSensitiveContentProtectionManagerService.mNotificationListener @@ -754,7 +766,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 // as non-sensitive setupSensitiveNotification(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); Mockito.reset(mWindowManager); when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1))).thenReturn(null); @@ -770,7 +782,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 // as non-sensitive setupSensitiveNotification(); - mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); mSensitiveContentProtectionManagerService.mNotificationListener .onNotificationPosted(mNotification1, mRankingMap); diff --git a/services/tests/selinux/AndroidTest.xml b/services/tests/selinux/AndroidTest.xml new file mode 100644 index 000000000000..16d8e0709345 --- /dev/null +++ b/services/tests/selinux/AndroidTest.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Runs Selinux Frameworks Tests."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-instrumentation" /> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="install-arg" value="-t" /> + <option name="test-file-name" value="SelinuxFrameworksTests.apk" /> + </target_preparer> + + <option name="test-tag" value="SelinuxFrameworksTests" /> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.frameworks.selinuxtests" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration> diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java index f1c1dc365b90..59f4d56b44f1 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java @@ -24,6 +24,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; @@ -315,7 +316,7 @@ public class AudioDeviceBrokerTest { mFakeBtDevice.getAddress())); verify(mMockAudioService, timeout(MAX_MESSAGE_HANDLING_DELAY_MS).times(0)).onUpdatedAdiDeviceState( - eq(devState)); + eq(devState), anyBoolean()); } // metadata set @@ -326,7 +327,7 @@ public class AudioDeviceBrokerTest { mFakeBtDevice.getAddress())); verify(mMockAudioService, timeout(MAX_MESSAGE_HANDLING_DELAY_MS)).onUpdatedAdiDeviceState( - any()); + any(), anyBoolean()); } } finally { // reset the metadata device type @@ -354,7 +355,7 @@ public class AudioDeviceBrokerTest { verify(mMockAudioService, timeout(MAX_MESSAGE_HANDLING_DELAY_MS).atLeast(1)).onUpdatedAdiDeviceState( ArgumentMatchers.argThat(devState -> devState.getAudioDeviceCategory() - == AudioManager.AUDIO_DEVICE_CATEGORY_OTHER)); + == AudioManager.AUDIO_DEVICE_CATEGORY_OTHER), anyBoolean()); } private void doTestConnectionDisconnectionReconnection(int delayAfterDisconnection, diff --git a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java index 081da11f2aa8..489ef4444e1d 100644 --- a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java +++ b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java @@ -66,6 +66,7 @@ import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.MockitoSession; import org.mockito.quality.Strictness; @@ -220,43 +221,36 @@ public class CrashRecoveryTest { RescuePartyObserver rescuePartyObserver = setUpRescuePartyObserver(watchdog); verify(rescuePartyObserver, never()).executeBootLoopMitigation(1); - int bootCounter = 0; + for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) { watchdog.noteBoot(); - bootCounter += 1; } + verify(rescuePartyObserver).executeBootLoopMitigation(1); verify(rescuePartyObserver, never()).executeBootLoopMitigation(2); - for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) { - watchdog.noteBoot(); - bootCounter += 1; - } + watchdog.noteBoot(); + verify(rescuePartyObserver).executeBootLoopMitigation(2); verify(rescuePartyObserver, never()).executeBootLoopMitigation(3); - int bootLoopThreshold = PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD - bootCounter; - for (int i = 0; i < bootLoopThreshold; i++) { - watchdog.noteBoot(); - } + watchdog.noteBoot(); + verify(rescuePartyObserver).executeBootLoopMitigation(3); verify(rescuePartyObserver, never()).executeBootLoopMitigation(4); - for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) { - watchdog.noteBoot(); - } + watchdog.noteBoot(); + verify(rescuePartyObserver).executeBootLoopMitigation(4); verify(rescuePartyObserver, never()).executeBootLoopMitigation(5); - for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) { - watchdog.noteBoot(); - } + watchdog.noteBoot(); + verify(rescuePartyObserver).executeBootLoopMitigation(5); verify(rescuePartyObserver, never()).executeBootLoopMitigation(6); - for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) { - watchdog.noteBoot(); - } + watchdog.noteBoot(); + verify(rescuePartyObserver).executeBootLoopMitigation(6); verify(rescuePartyObserver, never()).executeBootLoopMitigation(7); } @@ -268,11 +262,11 @@ public class CrashRecoveryTest { setUpRollbackPackageHealthObserver(watchdog); verify(rollbackObserver, never()).executeBootLoopMitigation(1); - int bootCounter = 0; + for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) { watchdog.noteBoot(); - bootCounter += 1; } + verify(rollbackObserver).executeBootLoopMitigation(1); verify(rollbackObserver, never()).executeBootLoopMitigation(2); @@ -280,19 +274,16 @@ public class CrashRecoveryTest { when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL)); - int bootLoopThreshold = PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD - bootCounter; - for (int i = 0; i < bootLoopThreshold; i++) { - watchdog.noteBoot(); - } + watchdog.noteBoot(); + verify(rollbackObserver).executeBootLoopMitigation(2); verify(rollbackObserver, never()).executeBootLoopMitigation(3); // Update the list of available rollbacks after executing bootloop mitigation once when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_MANUAL)); - for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) { - watchdog.noteBoot(); - } + watchdog.noteBoot(); + verify(rollbackObserver, never()).executeBootLoopMitigation(3); } @@ -305,27 +296,21 @@ public class CrashRecoveryTest { verify(rescuePartyObserver, never()).executeBootLoopMitigation(1); verify(rollbackObserver, never()).executeBootLoopMitigation(1); - int bootCounter = 0; for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) { watchdog.noteBoot(); - bootCounter += 1; } verify(rescuePartyObserver).executeBootLoopMitigation(1); verify(rescuePartyObserver, never()).executeBootLoopMitigation(2); verify(rollbackObserver, never()).executeBootLoopMitigation(1); - for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) { - watchdog.noteBoot(); - bootCounter += 1; - } + watchdog.noteBoot(); + verify(rescuePartyObserver).executeBootLoopMitigation(2); verify(rescuePartyObserver, never()).executeBootLoopMitigation(3); verify(rollbackObserver, never()).executeBootLoopMitigation(2); - for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) { - watchdog.noteBoot(); - bootCounter += 1; - } + watchdog.noteBoot(); + verify(rescuePartyObserver, never()).executeBootLoopMitigation(3); verify(rollbackObserver).executeBootLoopMitigation(1); verify(rollbackObserver, never()).executeBootLoopMitigation(2); @@ -333,43 +318,46 @@ public class CrashRecoveryTest { when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL)); - int bootLoopThreshold = PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD - bootCounter; - for (int i = 0; i < bootLoopThreshold; i++) { - watchdog.noteBoot(); - } + watchdog.noteBoot(); + verify(rescuePartyObserver).executeBootLoopMitigation(3); verify(rescuePartyObserver, never()).executeBootLoopMitigation(4); verify(rollbackObserver, never()).executeBootLoopMitigation(2); - for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) { - watchdog.noteBoot(); - } + watchdog.noteBoot(); + verify(rescuePartyObserver).executeBootLoopMitigation(4); verify(rescuePartyObserver, never()).executeBootLoopMitigation(5); verify(rollbackObserver, never()).executeBootLoopMitigation(2); - for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) { - watchdog.noteBoot(); - } + watchdog.noteBoot(); + verify(rescuePartyObserver).executeBootLoopMitigation(5); verify(rescuePartyObserver, never()).executeBootLoopMitigation(6); verify(rollbackObserver, never()).executeBootLoopMitigation(2); - for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) { - watchdog.noteBoot(); - } + watchdog.noteBoot(); + verify(rescuePartyObserver, never()).executeBootLoopMitigation(6); verify(rollbackObserver).executeBootLoopMitigation(2); verify(rollbackObserver, never()).executeBootLoopMitigation(3); // Update the list of available rollbacks after executing bootloop mitigation when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_MANUAL)); - for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) { - watchdog.noteBoot(); - } + watchdog.noteBoot(); + verify(rescuePartyObserver).executeBootLoopMitigation(6); verify(rescuePartyObserver, never()).executeBootLoopMitigation(7); verify(rollbackObserver, never()).executeBootLoopMitigation(3); + + moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_DEESCALATION_WINDOW_MS + 1); + Mockito.reset(rescuePartyObserver); + + for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) { + watchdog.noteBoot(); + } + verify(rescuePartyObserver).executeBootLoopMitigation(1); + verify(rescuePartyObserver, never()).executeBootLoopMitigation(2); } RollbackPackageHealthObserver setUpRollbackPackageHealthObserver(PackageWatchdog watchdog) { @@ -506,16 +494,9 @@ public class CrashRecoveryTest { } try { - if (Flags.recoverabilityDetection()) { - mSpyBootThreshold = spy(watchdog.new BootThreshold( - PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT, - PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS, - PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT)); - } else { - mSpyBootThreshold = spy(watchdog.new BootThreshold( + mSpyBootThreshold = spy(watchdog.new BootThreshold( PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT, PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS)); - } doAnswer((Answer<Integer>) invocationOnMock -> { String storedValue = mCrashRecoveryPropertiesMap @@ -640,5 +621,16 @@ public class CrashRecoveryTest { public long uptimeMillis() { return mUpTimeMillis; } + public void moveTimeForward(long milliSeconds) { + mUpTimeMillis += milliSeconds; + } + } + + private void moveTimeForwardAndDispatch(long milliSeconds) { + // Exhaust all due runnables now which shouldn't be executed after time-leap + mTestLooper.dispatchAll(); + mTestClock.moveTimeForward(milliSeconds); + mTestLooper.moveTimeForward(milliSeconds); + mTestLooper.dispatchAll(); } } diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java index 4f27e06083ba..093923f3ed53 100644 --- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java +++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java @@ -45,13 +45,13 @@ import android.os.test.TestLooper; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.DeviceConfig; import android.util.AtomicFile; +import android.util.LongArrayQueue; import android.util.Xml; -import android.utils.LongArrayQueue; -import android.utils.XmlUtils; import androidx.test.InstrumentationRegistry; import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.server.PackageWatchdog.HealthCheckState; @@ -1224,7 +1224,7 @@ public class PackageWatchdogTest { PackageWatchdog watchdog = createWatchdog(); TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1); watchdog.registerHealthObserver(bootObserver); - for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD; i++) { + for (int i = 0; i < 15; i++) { watchdog.noteBoot(); } assertThat(bootObserver.mitigatedBootLoop()).isTrue(); @@ -1262,22 +1262,6 @@ public class PackageWatchdogTest { } /** - * Ensure that boot loop mitigation is not done when the number of boots does not meet the - * threshold. - */ - @Test - public void testBootLoopDetection_doesNotMeetThresholdRecoverabilityHighImpact() { - PackageWatchdog watchdog = createWatchdog(); - TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_LEVEL_80); - watchdog.registerHealthObserver(bootObserver); - for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD - 1; i++) { - watchdog.noteBoot(); - } - assertThat(bootObserver.mitigatedBootLoop()).isFalse(); - } - - /** * Ensure that boot loop mitigation is done for the observer with the lowest user impact */ @Test @@ -1306,7 +1290,7 @@ public class PackageWatchdogTest { bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); watchdog.registerHealthObserver(bootObserver1); watchdog.registerHealthObserver(bootObserver2); - for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD; i++) { + for (int i = 0; i < 15; i++) { watchdog.noteBoot(); } assertThat(bootObserver1.mitigatedBootLoop()).isTrue(); @@ -1349,9 +1333,7 @@ public class PackageWatchdogTest { watchdog.noteBoot(); } for (int i = 0; i < 4; i++) { - for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; j++) { watchdog.noteBoot(); - } } moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_DEESCALATION_WINDOW_MS + 1); @@ -1360,38 +1342,7 @@ public class PackageWatchdogTest { watchdog.noteBoot(); } for (int i = 0; i < 4; i++) { - for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; j++) { watchdog.noteBoot(); - } - } - - assertThat(bootObserver.mBootMitigationCounts).isEqualTo(List.of(1, 2, 3, 4, 1, 2, 3, 4)); - } - - @Test - public void testMultipleBootLoopMitigationRecoverabilityHighImpact() { - PackageWatchdog watchdog = createWatchdog(); - TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_LEVEL_80); - watchdog.registerHealthObserver(bootObserver); - for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD - 1; j++) { - watchdog.noteBoot(); - } - for (int i = 0; i < 4; i++) { - for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; j++) { - watchdog.noteBoot(); - } - } - - moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_DEESCALATION_WINDOW_MS + 1); - - for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD - 1; j++) { - watchdog.noteBoot(); - } - for (int i = 0; i < 4; i++) { - for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; j++) { - watchdog.noteBoot(); - } } assertThat(bootObserver.mBootMitigationCounts).isEqualTo(List.of(1, 2, 3, 4, 1, 2, 3, 4)); @@ -1642,8 +1593,7 @@ public class PackageWatchdogTest { mSpyBootThreshold = spy(watchdog.new BootThreshold( PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT, - PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS, - PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT)); + PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS)); watchdog.saveAllObserversBootMitigationCountToMetadata(filePath); @@ -1798,16 +1748,9 @@ public class PackageWatchdogTest { mCrashRecoveryPropertiesMap = new HashMap<>(); try { - if (Flags.recoverabilityDetection()) { - mSpyBootThreshold = spy(watchdog.new BootThreshold( - PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT, - PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS, - PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT)); - } else { - mSpyBootThreshold = spy(watchdog.new BootThreshold( + mSpyBootThreshold = spy(watchdog.new BootThreshold( PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT, PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS)); - } doAnswer((Answer<Integer>) invocationOnMock -> { String storedValue = mCrashRecoveryPropertiesMap diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp index 682adbc86d06..30333da5e86c 100644 --- a/tools/hoststubgen/hoststubgen/Android.bp +++ b/tools/hoststubgen/hoststubgen/Android.bp @@ -82,30 +82,13 @@ java_library { jarjar_rules: "jarjar-rules.txt", } -// For sharing the code with other tools -java_library_host { - name: "hoststubgen-lib", - defaults: ["ravenwood-internal-only-visibility-java"], - srcs: ["src/**/*.kt"], - static_libs: [ - "hoststubgen-helper-runtime", - ], - libs: [ - "junit", - "ow2-asm", - "ow2-asm-analysis", - "ow2-asm-commons", - "ow2-asm-tree", - "ow2-asm-util", - ], -} - // Host-side stub generator tool. java_binary_host { name: "hoststubgen", - main_class: "com.android.hoststubgen.HostStubGenMain", + main_class: "com.android.hoststubgen.Main", + srcs: ["src/**/*.kt"], static_libs: [ - "hoststubgen-lib", + "hoststubgen-helper-runtime", "junit", "ow2-asm", "ow2-asm-analysis", diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt index 803dc283b8c7..1089f82b6472 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt @@ -32,6 +32,7 @@ import com.android.hoststubgen.visitors.PackageRedirectRemapper import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassVisitor import org.objectweb.asm.ClassWriter +import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.util.CheckClassAdapter import java.io.BufferedInputStream import java.io.FileOutputStream @@ -51,7 +52,7 @@ class HostStubGen(val options: HostStubGenOptions) { val stats = HostStubGenStats() // Load all classes. - val allClasses = ClassNodes.loadClassStructures(options.inJar.get) + val allClasses = loadClassStructures(options.inJar.get) // Dump the classes, if specified. options.inputJarDumpFile.ifSet { @@ -91,6 +92,55 @@ class HostStubGen(val options: HostStubGenOptions) { } /** + * Load all the classes, without code. + */ + private fun loadClassStructures(inJar: String): ClassNodes { + log.i("Reading class structure from $inJar ...") + val start = System.currentTimeMillis() + + val allClasses = ClassNodes() + + log.withIndent { + ZipFile(inJar).use { inZip -> + val inEntries = inZip.entries() + + while (inEntries.hasMoreElements()) { + val entry = inEntries.nextElement() + + BufferedInputStream(inZip.getInputStream(entry)).use { bis -> + if (entry.name.endsWith(".class")) { + val cr = ClassReader(bis) + val cn = ClassNode() + cr.accept(cn, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG + or ClassReader.SKIP_FRAMES) + if (!allClasses.addClass(cn)) { + log.w("Duplicate class found: ${cn.name}") + } + } else if (entry.name.endsWith(".dex")) { + // Seems like it's an ART jar file. We can't process it. + // It's a fatal error. + throw InvalidJarFileException( + "$inJar is not a desktop jar file. It contains a *.dex file.") + } else { + // Unknown file type. Skip. + while (bis.available() > 0) { + bis.skip((1024 * 1024).toLong()) + } + } + } + } + } + } + if (allClasses.size == 0) { + log.w("$inJar contains no *.class files.") + } + + val end = System.currentTimeMillis() + log.i("Done reading class structure in %.1f second(s).", (end - start) / 1000.0) + return allClasses + } + + /** * Build the filter, which decides what classes/methods/fields should be put in stub or impl * jars, and "how". (e.g. with substitution?) */ @@ -179,7 +229,7 @@ class HostStubGen(val options: HostStubGenOptions) { val intersectingJars = mutableMapOf<String, ClassNodes>() filenames.forEach { filename -> - intersectingJars[filename] = ClassNodes.loadClassStructures(filename) + intersectingJars[filename] = loadClassStructures(filename) } return intersectingJars } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt index 9ff798a4b5cb..9f5d524517d0 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt @@ -268,7 +268,7 @@ class HostStubGenOptions( } if (!ret.outStubJar.isSet && !ret.outImplJar.isSet) { log.w("Neither --out-stub-jar nor --out-impl-jar is set." + - " $executableName will not generate jar files.") + " $COMMAND_NAME will not generate jar files.") } if (ret.enableNonStubMethodCallDetection.get) { diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt index 45e7e301c0d1..4882c00d2b3c 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt @@ -13,18 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@file:JvmName("HostStubGenMain") +@file:JvmName("Main") package com.android.hoststubgen import java.io.PrintWriter +const val COMMAND_NAME = "HostStubGen" + /** * Entry point. */ fun main(args: Array<String>) { - executableName = "HostStubGen" - var success = false var clanupOnError = false @@ -33,7 +33,7 @@ fun main(args: Array<String>) { val options = HostStubGenOptions.parseArgs(args) clanupOnError = options.cleanUpOnError.get - log.v("$executableName started") + log.v("HostStubGen started") log.v("Options: $options") // Run. @@ -41,7 +41,7 @@ fun main(args: Array<String>) { success = true } catch (e: Throwable) { - log.e("$executableName: Error: ${e.message}") + log.e("$COMMAND_NAME: Error: ${e.message}") if (e !is UserErrorException) { e.printStackTrace(PrintWriter(log.getWriter(LogLevel.Error))) } @@ -49,7 +49,7 @@ fun main(args: Array<String>) { TODO("Remove output jars here") } } finally { - log.i("$executableName finished") + log.i("$COMMAND_NAME finished") log.flush() } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt index aa63d8d9f870..937e56c2cbb5 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt @@ -16,11 +16,6 @@ package com.android.hoststubgen /** - * Name of this executable. Set it in the main method. - */ -var executableName = "[command name not set]" - -/** * A regex that maches whitespate. */ val whitespaceRegex = """\s+""".toRegex() diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt index 83e122feeeb2..0579c2bb0525 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt @@ -34,9 +34,6 @@ val CLASS_INITIALIZER_NAME = "<clinit>" /** Descriptor of the class initializer method. */ val CLASS_INITIALIZER_DESC = "()V" -/** Name of constructors. */ -val CTOR_NAME = "<init>" - /** * Find any of [anyAnnotations] from the list of visible / invisible annotations. */ @@ -138,10 +135,10 @@ fun writeByteCodeToPushArguments( // Note, long and double will consume two local variable spaces, so the extra `i++`. when (type) { Type.VOID_TYPE -> throw HostStubGenInternalException("VOID_TYPE not expected") - Type.BOOLEAN_TYPE, Type.CHAR_TYPE, Type.BYTE_TYPE, Type.SHORT_TYPE, Type.INT_TYPE + Type.BOOLEAN_TYPE, Type.INT_TYPE, Type.SHORT_TYPE, Type.CHAR_TYPE -> writer.visitVarInsn(Opcodes.ILOAD, i) - Type.FLOAT_TYPE -> writer.visitVarInsn(Opcodes.FLOAD, i) Type.LONG_TYPE -> writer.visitVarInsn(Opcodes.LLOAD, i++) + Type.FLOAT_TYPE -> writer.visitVarInsn(Opcodes.FLOAD, i) Type.DOUBLE_TYPE -> writer.visitVarInsn(Opcodes.DLOAD, i++) else -> writer.visitVarInsn(Opcodes.ALOAD, i) } @@ -157,10 +154,10 @@ fun writeByteCodeToReturn(methodDescriptor: String, writer: MethodVisitor) { // See https://en.wikipedia.org/wiki/List_of_Java_bytecode_instructions when (type) { Type.VOID_TYPE -> writer.visitInsn(Opcodes.RETURN) - Type.BOOLEAN_TYPE, Type.CHAR_TYPE, Type.BYTE_TYPE, Type.SHORT_TYPE, Type.INT_TYPE + Type.BOOLEAN_TYPE, Type.INT_TYPE, Type.SHORT_TYPE, Type.CHAR_TYPE -> writer.visitInsn(Opcodes.IRETURN) - Type.FLOAT_TYPE -> writer.visitInsn(Opcodes.FRETURN) Type.LONG_TYPE -> writer.visitInsn(Opcodes.LRETURN) + Type.FLOAT_TYPE -> writer.visitInsn(Opcodes.FRETURN) Type.DOUBLE_TYPE -> writer.visitInsn(Opcodes.DRETURN) else -> writer.visitInsn(Opcodes.ARETURN) } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt index 92906a75b93a..bc34ef0dc8a7 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt @@ -16,18 +16,13 @@ package com.android.hoststubgen.asm import com.android.hoststubgen.ClassParseException -import com.android.hoststubgen.InvalidJarFileException -import com.android.hoststubgen.log -import org.objectweb.asm.ClassReader import org.objectweb.asm.tree.AnnotationNode import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.FieldNode import org.objectweb.asm.tree.MethodNode import org.objectweb.asm.tree.TypeAnnotationNode -import java.io.BufferedInputStream import java.io.PrintWriter import java.util.Arrays -import java.util.zip.ZipFile /** * Stores all classes loaded from a jar file, in a form of [ClassNode] @@ -67,8 +62,8 @@ class ClassNodes { /** Find a field, which may not exist. */ fun findField( - className: String, - fieldName: String, + className: String, + fieldName: String, ): FieldNode? { return findClass(className)?.fields?.firstOrNull { it.name == fieldName }?.let { fn -> return fn @@ -77,14 +72,14 @@ class ClassNodes { /** Find a method, which may not exist. */ fun findMethod( - className: String, - methodName: String, - descriptor: String, + className: String, + methodName: String, + descriptor: String, ): MethodNode? { return findClass(className)?.methods - ?.firstOrNull { it.name == methodName && it.desc == descriptor }?.let { mn -> - return mn - } + ?.firstOrNull { it.name == methodName && it.desc == descriptor }?.let { mn -> + return mn + } } /** @return true if a class has a class initializer. */ @@ -111,33 +106,26 @@ class ClassNodes { private fun dumpClass(pw: PrintWriter, cn: ClassNode) { pw.printf("Class: %s [access: %x]\n", cn.name, cn.access) - dumpAnnotations( - pw, " ", - cn.visibleTypeAnnotations, cn.invisibleTypeAnnotations, - cn.visibleAnnotations, cn.invisibleAnnotations, - ) + dumpAnnotations(pw, " ", + cn.visibleTypeAnnotations, cn.invisibleTypeAnnotations, + cn.visibleAnnotations, cn.invisibleAnnotations, + ) for (f in cn.fields ?: emptyList()) { - pw.printf( - " Field: %s [sig: %s] [desc: %s] [access: %x]\n", - f.name, f.signature, f.desc, f.access - ) - dumpAnnotations( - pw, " ", - f.visibleTypeAnnotations, f.invisibleTypeAnnotations, - f.visibleAnnotations, f.invisibleAnnotations, - ) + pw.printf(" Field: %s [sig: %s] [desc: %s] [access: %x]\n", + f.name, f.signature, f.desc, f.access) + dumpAnnotations(pw, " ", + f.visibleTypeAnnotations, f.invisibleTypeAnnotations, + f.visibleAnnotations, f.invisibleAnnotations, + ) } for (m in cn.methods ?: emptyList()) { - pw.printf( - " Method: %s [sig: %s] [desc: %s] [access: %x]\n", - m.name, m.signature, m.desc, m.access - ) - dumpAnnotations( - pw, " ", - m.visibleTypeAnnotations, m.invisibleTypeAnnotations, - m.visibleAnnotations, m.invisibleAnnotations, - ) + pw.printf(" Method: %s [sig: %s] [desc: %s] [access: %x]\n", + m.name, m.signature, m.desc, m.access) + dumpAnnotations(pw, " ", + m.visibleTypeAnnotations, m.invisibleTypeAnnotations, + m.visibleAnnotations, m.invisibleAnnotations, + ) } } @@ -148,7 +136,7 @@ class ClassNodes { invisibleTypeAnnotations: List<TypeAnnotationNode>?, visibleAnnotations: List<AnnotationNode>?, invisibleAnnotations: List<AnnotationNode>?, - ) { + ) { for (an in visibleTypeAnnotations ?: emptyList()) { pw.printf("%sTypeAnnotation(vis): %s\n", prefix, an.desc) } @@ -178,55 +166,4 @@ class ClassNodes { } } } - - companion object { - /** - * Load all the classes, without code. - */ - fun loadClassStructures(inJar: String): ClassNodes { - log.i("Reading class structure from $inJar ...") - val start = System.currentTimeMillis() - - val allClasses = ClassNodes() - - log.withIndent { - ZipFile(inJar).use { inZip -> - val inEntries = inZip.entries() - - while (inEntries.hasMoreElements()) { - val entry = inEntries.nextElement() - - BufferedInputStream(inZip.getInputStream(entry)).use { bis -> - if (entry.name.endsWith(".class")) { - val cr = ClassReader(bis) - val cn = ClassNode() - cr.accept(cn, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG - or ClassReader.SKIP_FRAMES) - if (!allClasses.addClass(cn)) { - log.w("Duplicate class found: ${cn.name}") - } - } else if (entry.name.endsWith(".dex")) { - // Seems like it's an ART jar file. We can't process it. - // It's a fatal error. - throw InvalidJarFileException( - "$inJar is not a desktop jar file. It contains a *.dex file.") - } else { - // Unknown file type. Skip. - while (bis.available() > 0) { - bis.skip((1024 * 1024).toLong()) - } - } - } - } - } - } - if (allClasses.size == 0) { - log.w("$inJar contains no *.class files.") - } - - val end = System.currentTimeMillis() - log.i("Done reading class structure in %.1f second(s).", (end - start) / 1000.0) - return allClasses - } - } }
\ No newline at end of file diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt index 5a26fc69d473..78b13fd36f06 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt @@ -19,14 +19,14 @@ import com.android.hoststubgen.HostStubGenErrors import com.android.hoststubgen.HostStubGenInternalException import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME +import com.android.hoststubgen.asm.isAnonymousInnerClass +import com.android.hoststubgen.log import com.android.hoststubgen.asm.ClassNodes import com.android.hoststubgen.asm.isAnnotation -import com.android.hoststubgen.asm.isAnonymousInnerClass import com.android.hoststubgen.asm.isAutoGeneratedEnumMember import com.android.hoststubgen.asm.isEnum import com.android.hoststubgen.asm.isSynthetic import com.android.hoststubgen.asm.isVisibilityPrivateOrPackagePrivate -import com.android.hoststubgen.log import org.objectweb.asm.tree.ClassNode /** diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt index fa8fe6cd384f..f70a17d9b7cd 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt @@ -1833,7 +1833,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative super_class: #x // java/lang/Object - interfaces: 0, fields: 1, methods: 11, attributes: 2 + interfaces: 0, fields: 1, methods: 10, attributes: 2 int value; descriptor: I flags: (0x0000) @@ -1938,10 +1938,6 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative x: invokespecial #x // Method java/lang/RuntimeException."<init>":()V x: athrow LineNumberTable: - - public static native byte nativeBytePlus(byte, byte); - descriptor: (BB)B - flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE } SourceFile: "TinyFrameworkNative.java" RuntimeInvisibleAnnotations: @@ -1959,7 +1955,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host super_class: #x // java/lang/Object - interfaces: 0, fields: 0, methods: 5, attributes: 2 + interfaces: 0, fields: 0, methods: 4, attributes: 2 public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -2017,22 +2013,6 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host Start Length Slot Name Signature 0 7 0 source Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative; 0 7 1 arg I - - public static byte nativeBytePlus(byte, byte); - descriptor: (BB)B - flags: (0x0009) ACC_PUBLIC, ACC_STATIC - Code: - stack=2, locals=2, args_size=2 - x: iload_0 - x: iload_1 - x: iadd - x: i2b - x: ireturn - LineNumberTable: - LocalVariableTable: - Start Length Slot Name Signature - 0 5 0 arg1 B - 0 5 1 arg2 B } SourceFile: "TinyFrameworkNative_host.java" RuntimeInvisibleAnnotations: diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt index c605f767f527..37de857b9780 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt @@ -1554,7 +1554,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative super_class: #x // java/lang/Object - interfaces: 0, fields: 1, methods: 10, attributes: 3 + interfaces: 0, fields: 1, methods: 9, attributes: 3 int value; descriptor: I flags: (0x0000) @@ -1686,15 +1686,6 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative com.android.hoststubgen.hosthelper.HostStubGenKeptInStub x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - - public static native byte nativeBytePlus(byte, byte); - descriptor: (BB)B - flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE - RuntimeVisibleAnnotations: - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInStub - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl } SourceFile: "TinyFrameworkNative.java" RuntimeVisibleAnnotations: diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt index 11d5939b7917..c9c607c58c68 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt @@ -2236,7 +2236,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative super_class: #x // java/lang/Object - interfaces: 0, fields: 1, methods: 11, attributes: 3 + interfaces: 0, fields: 1, methods: 10, attributes: 3 int value; descriptor: I flags: (0x0000) @@ -2435,23 +2435,6 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative com.android.hoststubgen.hosthelper.HostStubGenKeptInStub x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - - public static byte nativeBytePlus(byte, byte); - descriptor: (BB)B - flags: (0x0009) ACC_PUBLIC, ACC_STATIC - Code: - stack=2, locals=2, args_size=2 - x: iload_0 - x: iload_1 - x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeBytePlus:(BB)B - x: ireturn - RuntimeVisibleAnnotations: - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInStub - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl } SourceFile: "TinyFrameworkNative.java" RuntimeVisibleAnnotations: @@ -2474,7 +2457,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host super_class: #x // java/lang/Object - interfaces: 0, fields: 0, methods: 5, attributes: 3 + interfaces: 0, fields: 0, methods: 4, attributes: 3 public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -2568,31 +2551,6 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - - public static byte nativeBytePlus(byte, byte); - descriptor: (BB)B - flags: (0x0009) ACC_PUBLIC, ACC_STATIC - Code: - stack=4, locals=2, args_size=2 - x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host - x: ldc #x // String nativeBytePlus - x: ldc #x // String (BB)B - x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; - x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; - x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V - x: iload_0 - x: iload_1 - x: iadd - x: i2b - x: ireturn - LineNumberTable: - LocalVariableTable: - Start Length Slot Name Signature - 15 5 0 arg1 B - 15 5 1 arg2 B - RuntimeVisibleAnnotations: - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl } SourceFile: "TinyFrameworkNative_host.java" RuntimeVisibleAnnotations: diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt index c605f767f527..37de857b9780 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt @@ -1554,7 +1554,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative super_class: #x // java/lang/Object - interfaces: 0, fields: 1, methods: 10, attributes: 3 + interfaces: 0, fields: 1, methods: 9, attributes: 3 int value; descriptor: I flags: (0x0000) @@ -1686,15 +1686,6 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative com.android.hoststubgen.hosthelper.HostStubGenKeptInStub x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - - public static native byte nativeBytePlus(byte, byte); - descriptor: (BB)B - flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE - RuntimeVisibleAnnotations: - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInStub - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl } SourceFile: "TinyFrameworkNative.java" RuntimeVisibleAnnotations: diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt index 088bc80e11c5..a57907d9398b 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt @@ -2743,7 +2743,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative super_class: #x // java/lang/Object - interfaces: 0, fields: 1, methods: 12, attributes: 3 + interfaces: 0, fields: 1, methods: 11, attributes: 3 int value; descriptor: I flags: (0x0000) @@ -3002,28 +3002,6 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative com.android.hoststubgen.hosthelper.HostStubGenKeptInStub x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - - public static byte nativeBytePlus(byte, byte); - descriptor: (BB)B - flags: (0x0009) ACC_PUBLIC, ACC_STATIC - Code: - stack=4, locals=2, args_size=2 - x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative - x: ldc #x // String nativeBytePlus - x: ldc #x // String (BB)B - x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall - x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - x: iload_0 - x: iload_1 - x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeBytePlus:(BB)B - x: ireturn - RuntimeVisibleAnnotations: - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInStub - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl } SourceFile: "TinyFrameworkNative.java" RuntimeVisibleAnnotations: @@ -3046,7 +3024,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host super_class: #x // java/lang/Object - interfaces: 0, fields: 0, methods: 6, attributes: 3 + interfaces: 0, fields: 0, methods: 5, attributes: 3 private static {}; descriptor: ()V flags: (0x000a) ACC_PRIVATE, ACC_STATIC @@ -3170,36 +3148,6 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl - - public static byte nativeBytePlus(byte, byte); - descriptor: (BB)B - flags: (0x0009) ACC_PUBLIC, ACC_STATIC - Code: - stack=4, locals=2, args_size=2 - x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host - x: ldc #x // String nativeBytePlus - x: ldc #x // String (BB)B - x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall - x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host - x: ldc #x // String nativeBytePlus - x: ldc #x // String (BB)B - x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; - x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; - x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V - x: iload_0 - x: iload_1 - x: iadd - x: i2b - x: ireturn - LineNumberTable: - LocalVariableTable: - Start Length Slot Name Signature - 26 5 0 arg1 B - 26 5 1 arg2 B - RuntimeVisibleAnnotations: - x: #x() - com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl } SourceFile: "TinyFrameworkNative_host.java" RuntimeVisibleAnnotations: diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java index 09ee183a2dcc..5a5e22db59e5 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java @@ -52,6 +52,4 @@ public class TinyFrameworkNative { public static void nativeStillNotSupported_should_be_like_this() { throw new RuntimeException(); } - - public static native byte nativeBytePlus(byte arg1, byte arg2); } diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java index b23c21602967..749ebaa378e3 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java @@ -34,8 +34,4 @@ public class TinyFrameworkNative_host { public static int nativeNonStaticAddToValue(TinyFrameworkNative source, int arg) { return source.value + arg; } - - public static byte nativeBytePlus(byte arg1, byte arg2) { - return (byte) (arg1 + arg2); - } } diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java index 762180dcf74b..ba17c75132f2 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java @@ -154,22 +154,13 @@ public class TinyFrameworkClassTest { } @Test - public void testNativeSubstitutionLong() { - assertThat(TinyFrameworkNative.nativeLongPlus(1L, 2L)).isEqualTo(3L); - } - - @Test - public void testNativeSubstitutionByte() { - assertThat(TinyFrameworkNative.nativeBytePlus((byte) 3, (byte) 4)).isEqualTo(7); - } - - @Test public void testNativeSubstitutionClass_nonStatic() { TinyFrameworkNative instance = new TinyFrameworkNative(); instance.setValue(5); assertThat(instance.nativeNonStaticAddToValue(3)).isEqualTo(8); } + @Test public void testSubstituteNativeWithThrow() throws Exception { // We can't use TinyFrameworkNative.nativeStillNotSupported() directly in this class, |