Merge "Remove visitPersonUris flag" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index e551789..fda66fa 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -21016,6 +21016,7 @@
method @Deprecated public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface();
method public android.view.View onCreateInputView();
method protected void onCurrentInputMethodSubtypeChanged(android.view.inputmethod.InputMethodSubtype);
+ method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public void onCustomImeSwitcherButtonRequestedVisible(boolean);
method public void onDisplayCompletions(android.view.inputmethod.CompletionInfo[]);
method public boolean onEvaluateFullscreenMode();
method @CallSuper public boolean onEvaluateInputViewShown();
diff --git a/core/java/android/app/supervision/flags.aconfig b/core/java/android/app/supervision/flags.aconfig
index bcb5b36..d5e696d 100644
--- a/core/java/android/app/supervision/flags.aconfig
+++ b/core/java/android/app/supervision/flags.aconfig
@@ -7,4 +7,12 @@
namespace: "supervision"
description: "Flag to enable the SupervisionService"
bug: "340351729"
-}
\ No newline at end of file
+}
+
+flag {
+ name: "supervision_api_on_wear"
+ is_exported: true
+ namespace: "supervision"
+ description: "Flag to enable the SupervisionService on Wear devices"
+ bug: "373358935"
+}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 34c3f57..e9e8578 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -38,6 +38,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AttributeSet;
+import android.util.EmptyArray;
import android.util.Pair;
import android.util.Slog;
@@ -565,10 +566,7 @@
usesSdkLibrariesVersionsMajor, usesSdkLibVersionMajor,
/*allowDuplicates=*/ true);
- // We allow ":" delimiters in the SHA declaration as this is the format
- // emitted by the certtool making it easy for developers to copy/paste.
- // TODO(372862145): Add test for this replacement
- usesSdkCertDigest = usesSdkCertDigest.replace(":", "").toLowerCase();
+ usesSdkCertDigest = normalizeCertDigest(usesSdkCertDigest);
if ("".equals(usesSdkCertDigest)) {
// Test-only uses-sdk-library empty certificate digest override.
@@ -618,18 +616,23 @@
usesStaticLibrariesVersions, usesStaticLibVersion,
/*allowDuplicates=*/ true);
- // We allow ":" delimiters in the SHA declaration as this is the format
- // emitted by the certtool making it easy for developers to copy/paste.
- // TODO(372862145): Add test for this replacement
- usesStaticLibCertDigest =
- usesStaticLibCertDigest.replace(":", "").toLowerCase();
+ usesStaticLibCertDigest = normalizeCertDigest(usesStaticLibCertDigest);
- // TODO(372862145): Add support for multiple signer for app targeting
- // O-MR1
+ ParseResult<String[]> certResult =
+ parseAdditionalCertificates(input, parser);
+ if (certResult.isError()) {
+ return input.error(certResult);
+ }
+ String[] additionalCertSha256Digests = certResult.getResult();
+ String[] certSha256Digests =
+ new String[additionalCertSha256Digests.length + 1];
+ certSha256Digests[0] = usesStaticLibCertDigest;
+ System.arraycopy(additionalCertSha256Digests, 0, certSha256Digests,
+ 1, additionalCertSha256Digests.length);
+
usesStaticLibrariesCertDigests = ArrayUtils.appendElement(
String[].class, usesStaticLibrariesCertDigests,
- new String[]{usesStaticLibCertDigest},
- /*allowDuplicates=*/ true);
+ certSha256Digests, /*allowDuplicates=*/ true);
break;
case TAG_SDK_LIBRARY:
isSdkLibrary = true;
@@ -809,6 +812,43 @@
declaredLibraries));
}
+ private static ParseResult<String[]> parseAdditionalCertificates(ParseInput input,
+ XmlResourceParser parser) throws XmlPullParserException, IOException {
+ String[] certSha256Digests = EmptyArray.STRING;
+ final int depth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > depth)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ final String nodeName = parser.getName();
+ if (nodeName.equals("additional-certificate")) {
+ String certSha256Digest = parser.getAttributeValue(
+ ANDROID_RES_NAMESPACE, "certDigest");
+ if (TextUtils.isEmpty(certSha256Digest)) {
+ return input.error("Bad additional-certificate declaration with empty"
+ + " certDigest:" + certSha256Digest);
+ }
+
+ certSha256Digest = normalizeCertDigest(certSha256Digest);
+ certSha256Digests = ArrayUtils.appendElement(String.class,
+ certSha256Digests, certSha256Digest);
+ }
+ }
+
+ return input.success(certSha256Digests);
+ }
+
+ /**
+ * We allow ":" delimiters in the SHA declaration as this is the format emitted by the
+ * certtool making it easy for developers to copy/paste.
+ */
+ private static String normalizeCertDigest(String certDigest) {
+ return certDigest.replace(":", "").toLowerCase();
+ }
+
private static boolean isDeviceAdminReceiver(
XmlResourceParser parser, boolean applicationHasBindDeviceAdminPermission)
throws XmlPullParserException, IOException {
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 8c3f0ef..ae83668 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -55,6 +55,7 @@
import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_OTHER;
import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED;
import static android.view.inputmethod.Flags.FLAG_CONNECTIONLESS_HANDWRITING;
+import static android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API;
import static android.view.inputmethod.Flags.ctrlShiftShortcut;
import static android.view.inputmethod.Flags.predictiveBackIme;
@@ -4392,6 +4393,39 @@
}
/**
+ * Called when the requested visibility of a custom IME Switcher button changes.
+ *
+ * <p>When the system provides an IME navigation bar, it may decide to show an IME Switcher
+ * button inside this bar. However, the IME can request hiding the bar provided by the system
+ * with {@code getWindowInsetsController().hide(captionBar())} (the IME navigation bar provides
+ * {@link Type#captionBar() captionBar} insets to the IME window). If the request is successful,
+ * then it becomes the IME's responsibility to provide a custom IME Switcher button in its
+ * input view, with equivalent functionality.</p>
+ *
+ * <p>This custom button is only requested to be visible when the system provides the IME
+ * navigation bar, both the bar and the IME Switcher button inside it should be visible,
+ * but the IME successfully requested to hide the bar. This does not depend on the current
+ * visibility of the IME. It could be called with {@code true} while the IME is hidden, in
+ * which case the IME should prepare to show the button as soon as the IME itself is shown.</p>
+ *
+ * <p>This is only called when the requested visibility changes. The default value is
+ * {@code false} and as such, this will not be called initially if the resulting value is
+ * {@code false}.</p>
+ *
+ * <p>This can be called at any time after {@link #onCreate}, even if the IME is not currently
+ * visible. However, this is not guaranteed to be called before the IME is shown, as it depends
+ * on when the IME requested hiding the IME navigation bar. If the request is sent during
+ * the showing flow (e.g. during {@link #onStartInputView}), this will be called shortly after
+ * {@link #onWindowShown}, but before the first IME frame is drawn.</p>
+ *
+ * @param visible whether the button is requested visible or not.
+ */
+ @FlaggedApi(FLAG_IME_SWITCHER_REVAMP_API)
+ public void onCustomImeSwitcherButtonRequestedVisible(boolean visible) {
+ // Intentionally empty
+ }
+
+ /**
* Called when the IME switch button was clicked from the client. Depending on the number of
* enabled IME subtypes, this will either switch to the next IME/subtype, or show the input
* method picker dialog.
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index b08454d..38be8d9 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -41,6 +41,7 @@
import android.view.WindowInsetsController.Appearance;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;
@@ -178,6 +179,9 @@
private boolean mDrawLegacyNavigationBarBackground;
+ /** Whether a custom IME Switcher button should be visible. */
+ private boolean mCustomImeSwitcherVisible;
+
private final Rect mTempRect = new Rect();
private final int[] mTempPos = new int[2];
@@ -265,6 +269,7 @@
// IME navigation bar.
boolean visible = insets.isVisible(captionBar());
mNavigationBarFrame.setVisibility(visible ? View.VISIBLE : View.GONE);
+ checkCustomImeSwitcherVisibility();
}
return view.onApplyWindowInsets(insets);
});
@@ -491,6 +496,8 @@
mShouldShowImeSwitcherWhenImeIsShown;
mShouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherWhenImeIsShown;
+ checkCustomImeSwitcherVisibility();
+
mService.mWindow.getWindow().getDecorView().getWindowInsetsController()
.setImeCaptionBarInsetsHeight(getImeCaptionBarHeight(imeDrawsImeNavBar));
@@ -616,12 +623,33 @@
&& mNavigationBarFrame.getVisibility() == View.VISIBLE;
}
+ /**
+ * Checks if a custom IME Switcher button should be visible, and notifies the IME when this
+ * state changes. This can only be {@code true} if three conditions are met:
+ *
+ * <li>The IME should draw the IME navigation bar.</li>
+ * <li>The IME Switcher button should be visible when the IME is visible.</li>
+ * <li>The IME navigation bar should be visible, but was requested hidden by the IME.</li>
+ */
+ private void checkCustomImeSwitcherVisibility() {
+ if (!Flags.imeSwitcherRevampApi()) {
+ return;
+ }
+ final boolean visible = mImeDrawsImeNavBar && mShouldShowImeSwitcherWhenImeIsShown
+ && mNavigationBarFrame != null && !isShown();
+ if (visible != mCustomImeSwitcherVisible) {
+ mCustomImeSwitcherVisible = visible;
+ mService.onCustomImeSwitcherButtonRequestedVisible(mCustomImeSwitcherVisible);
+ }
+ }
+
@Override
public String toDebugString() {
return "{mImeDrawsImeNavBar=" + mImeDrawsImeNavBar
+ " mNavigationBarFrame=" + mNavigationBarFrame
+ " mShouldShowImeSwitcherWhenImeIsShown="
+ mShouldShowImeSwitcherWhenImeIsShown
+ + " mCustomImeSwitcherVisible=" + mCustomImeSwitcherVisible
+ " mAppearance=0x" + Integer.toHexString(mAppearance)
+ " mDarkIntensity=" + mDarkIntensity
+ " mDrawLegacyNavigationBarBackground=" + mDrawLegacyNavigationBarBackground
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index a382d79..f39508d 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -151,6 +151,7 @@
":HelloWorldUsingSdk1And2",
":HelloWorldUsingSdkMalformedNegativeVersion",
":CtsStaticSharedLibConsumerApp1",
+ ":CtsStaticSharedLibConsumerApp3",
],
}
diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml
index 3f7c83a..5d8ff87 100644
--- a/core/tests/coretests/AndroidTest.xml
+++ b/core/tests/coretests/AndroidTest.xml
@@ -41,6 +41,8 @@
value="/data/local/tmp/tests/coretests/pm/HelloWorldSdk1.apk"/>
<option name="push-file" key="CtsStaticSharedLibConsumerApp1.apk"
value="/data/local/tmp/tests/coretests/pm/CtsStaticSharedLibConsumerApp1.apk"/>
+ <option name="push-file" key="CtsStaticSharedLibConsumerApp3.apk"
+ value="/data/local/tmp/tests/coretests/pm/CtsStaticSharedLibConsumerApp3.apk"/>
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
diff --git a/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java b/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java
index d4618d7..0db49a7 100644
--- a/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java
+++ b/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java
@@ -72,6 +72,12 @@
private static final String TEST_APP_USING_SDK_MALFORMED_VERSION =
"HelloWorldUsingSdkMalformedNegativeVersion.apk";
private static final String TEST_APP_USING_STATIC_LIB = "CtsStaticSharedLibConsumerApp1.apk";
+ private static final String TEST_APP_USING_STATIC_LIB_TWO_CERTS =
+ "CtsStaticSharedLibConsumerApp3.apk";
+ private static final String STATIC_LIB_CERT_1 =
+ "70fbd440503ec0bf41f3f21fcc83ffd39880133c27deb0945ed677c6f31d72fb";
+ private static final String STATIC_LIB_CERT_2 =
+ "e49582ff3a0aa4c5589fc5feaac6b7d6e757199dd0c6742df7bf37c2ffef95f5";
private static final String TEST_SDK1 = "HelloWorldSdk1.apk";
private static final String TEST_SDK1_PACKAGE = "com.test.sdk1_1";
private static final String TEST_SDK1_NAME = "com.test.sdk1";
@@ -86,7 +92,7 @@
@Before
public void setUp() throws IOException {
- mTmpDir = mTemporaryFolder.newFolder("DexMetadataHelperTest");
+ mTmpDir = mTemporaryFolder.newFolder("ApkLiteParseUtilsTest");
}
@After
@@ -108,9 +114,8 @@
assertThat(baseApk.getUsesSdkLibrariesVersionsMajor()).asList().containsExactly(
TEST_SDK1_VERSION, TEST_SDK2_VERSION
);
- for (String[] certDigests: baseApk.getUsesSdkLibrariesCertDigests()) {
- assertThat(certDigests).asList().containsExactly("");
- }
+ String[][] expectedCerts = {{""}, {""}};
+ assertThat(baseApk.getUsesSdkLibrariesCertDigests()).isEqualTo(expectedCerts);
}
@SuppressLint("CheckResult")
@@ -126,18 +131,13 @@
ApkLite baseApk = result.getResult();
String[][] liteCerts = baseApk.getUsesSdkLibrariesCertDigests();
- assertThat(liteCerts).isNotNull();
- for (String[] certDigests: liteCerts) {
- assertThat(certDigests).asList().containsExactly(certDigest);
- }
+ String[][] expectedCerts = {{certDigest}, {certDigest}};
+ assertThat(liteCerts).isEqualTo(expectedCerts);
// Same for package parser
AndroidPackage pkg = mPackageParser2.parsePackage(apkFile, 0, true).hideAsFinal();
String[][] pkgCerts = pkg.getUsesSdkLibrariesCertDigests();
- assertThat(pkgCerts).isNotNull();
- for (int i = 0; i < liteCerts.length; i++) {
- assertThat(liteCerts[i]).isEqualTo(pkgCerts[i]);
- }
+ assertThat(liteCerts).isEqualTo(pkgCerts);
}
@@ -160,9 +160,7 @@
String[][] liteCerts = baseApk.getUsesSdkLibrariesCertDigests();
String[][] pkgCerts = pkg.getUsesSdkLibrariesCertDigests();
- for (int i = 0; i < liteCerts.length; i++) {
- assertThat(liteCerts[i]).isEqualTo(pkgCerts[i]);
- }
+ assertThat(liteCerts).isEqualTo(pkgCerts);
}
@SuppressLint("CheckResult")
@@ -184,9 +182,27 @@
String[][] liteCerts = baseApk.getUsesStaticLibrariesCertDigests();
String[][] pkgCerts = pkg.getUsesStaticLibrariesCertDigests();
- for (int i = 0; i < liteCerts.length; i++) {
- assertThat(liteCerts[i]).isEqualTo(pkgCerts[i]);
- }
+ assertThat(liteCerts).isEqualTo(pkgCerts);
+ }
+
+ @Test
+ public void testParseApkLite_getUsesStaticLibrary_twoCerts()
+ throws Exception {
+ File apkFile = copyApkToTmpDir(TEST_APP_USING_STATIC_LIB_TWO_CERTS);
+ ParseResult<ApkLite> result = ApkLiteParseUtils
+ .parseApkLite(ParseTypeImpl.forDefaultParsing().reset(), apkFile, 0);
+ assertThat(result.isError()).isFalse();
+ ApkLite baseApk = result.getResult();
+
+ // There are two certs.
+ String[][] expectedCerts = {{STATIC_LIB_CERT_1, STATIC_LIB_CERT_2}};
+ String[][] liteCerts = baseApk.getUsesStaticLibrariesCertDigests();
+ assertThat(liteCerts).isEqualTo(expectedCerts);
+
+ // And they are same as package parser.
+ AndroidPackage pkg = mPackageParser2.parsePackage(apkFile, 0, true).hideAsFinal();
+ String[][] pkgCerts = pkg.getUsesStaticLibrariesCertDigests();
+ assertThat(liteCerts).isEqualTo(pkgCerts);
}
@SuppressLint("CheckResult")
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
index 0b515f5..5f42bb1 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
@@ -475,6 +475,6 @@
override fun hideCurrentInputMethod() {}
- override fun updateBubbleBarLocation(location: BubbleBarLocation) {}
+ override fun updateBubbleBarLocation(location: BubbleBarLocation, source: Int) {}
}
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
index 0d742cc..6ac36a3 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
@@ -375,7 +375,7 @@
override fun hideCurrentInputMethod() {
}
- override fun updateBubbleBarLocation(location: BubbleBarLocation) {
+ override fun updateBubbleBarLocation(location: BubbleBarLocation, source: Int) {
}
}
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
index 00d9a93..0044593 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
@@ -351,7 +351,7 @@
override fun hideCurrentInputMethod() {}
- override fun updateBubbleBarLocation(location: BubbleBarLocation) {}
+ override fun updateBubbleBarLocation(location: BubbleBarLocation, source: Int) {}
}
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt
index 191875d..84a22b8 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt
@@ -15,6 +15,7 @@
*/
package com.android.wm.shell.shared.bubbles
+import android.annotation.IntDef
import android.os.Parcel
import android.os.Parcelable
@@ -60,4 +61,36 @@
override fun newArray(size: Int) = arrayOfNulls<BubbleBarLocation>(size)
}
}
+
+ /** Define set of constants that allow to determine why location changed. */
+ @IntDef(
+ UpdateSource.DRAG_BAR,
+ UpdateSource.DRAG_BUBBLE,
+ UpdateSource.DRAG_EXP_VIEW,
+ UpdateSource.A11Y_ACTION_BAR,
+ UpdateSource.A11Y_ACTION_BUBBLE,
+ UpdateSource.A11Y_ACTION_EXP_VIEW,
+ )
+ @Retention(AnnotationRetention.SOURCE)
+ annotation class UpdateSource {
+ companion object {
+ /** Location changed from dragging the bar */
+ const val DRAG_BAR = 1
+
+ /** Location changed from dragging the bubble */
+ const val DRAG_BUBBLE = 2
+
+ /** Location changed from dragging the expanded view */
+ const val DRAG_EXP_VIEW = 3
+
+ /** Location changed via a11y action on the bar */
+ const val A11Y_ACTION_BAR = 4
+
+ /** Location changed via a11y action on the bubble */
+ const val A11Y_ACTION_BUBBLE = 5
+
+ /** Location changed via a11y action on the expanded view */
+ const val A11Y_ACTION_EXP_VIEW = 6
+ }
+ }
}
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 14f8cc7..0fd98ed 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
@@ -740,8 +740,10 @@
/**
* Update bubble bar location and trigger and update to listeners
*/
- public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
+ public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation,
+ @BubbleBarLocation.UpdateSource int source) {
if (canShowAsBubbleBar()) {
+ BubbleBarLocation previousLocation = mBubblePositioner.getBubbleBarLocation();
mBubblePositioner.setBubbleBarLocation(bubbleBarLocation);
if (mLayerView != null && !mLayerView.isExpandedViewDragged()) {
mLayerView.updateExpandedView();
@@ -749,13 +751,47 @@
BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
bubbleBarUpdate.bubbleBarLocation = bubbleBarLocation;
mBubbleStateListener.onBubbleStateChange(bubbleBarUpdate);
+
+ logBubbleBarLocationIfChanged(bubbleBarLocation, previousLocation, source);
+ }
+ }
+
+ private void logBubbleBarLocationIfChanged(BubbleBarLocation location,
+ BubbleBarLocation previous,
+ @BubbleBarLocation.UpdateSource int source) {
+ if (mLayerView == null) {
+ return;
+ }
+ boolean isRtl = mLayerView.isLayoutRtl();
+ boolean wasLeft = previous.isOnLeft(isRtl);
+ boolean onLeft = location.isOnLeft(isRtl);
+ if (wasLeft == onLeft) {
+ // No changes, skip logging
+ return;
+ }
+ switch (source) {
+ case BubbleBarLocation.UpdateSource.DRAG_BAR:
+ case BubbleBarLocation.UpdateSource.A11Y_ACTION_BAR:
+ mLogger.log(onLeft ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BAR
+ : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BAR);
+ break;
+ case BubbleBarLocation.UpdateSource.DRAG_BUBBLE:
+ case BubbleBarLocation.UpdateSource.A11Y_ACTION_BUBBLE:
+ mLogger.log(onLeft ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BUBBLE
+ : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BUBBLE);
+ break;
+ case BubbleBarLocation.UpdateSource.DRAG_EXP_VIEW:
+ case BubbleBarLocation.UpdateSource.A11Y_ACTION_EXP_VIEW:
+ // TODO(b/349845968): move logging from BubbleBarLayerView to here
+ break;
}
}
/**
* Animate bubble bar to the given location. The location change is transient. It does not
* update the state of the bubble bar.
- * To update bubble bar pinned location, use {@link #setBubbleBarLocation(BubbleBarLocation)}.
+ * To update bubble bar pinned location, use
+ * {@link #setBubbleBarLocation(BubbleBarLocation, int)}.
*/
public void animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
if (canShowAsBubbleBar()) {
@@ -2568,9 +2604,10 @@
}
@Override
- public void setBubbleBarLocation(BubbleBarLocation location) {
+ public void setBubbleBarLocation(BubbleBarLocation location,
+ @BubbleBarLocation.UpdateSource int source) {
mMainExecutor.execute(() ->
- mController.setBubbleBarLocation(location));
+ mController.setBubbleBarLocation(location, source));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt
index ec4854b..6423eed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt
@@ -32,7 +32,10 @@
fun isStackExpanded(): Boolean
fun isShowingAsBubbleBar(): Boolean
fun hideCurrentInputMethod()
- fun updateBubbleBarLocation(location: BubbleBarLocation)
+ fun updateBubbleBarLocation(
+ location: BubbleBarLocation,
+ @BubbleBarLocation.UpdateSource source: Int,
+ )
companion object {
/**
@@ -82,8 +85,11 @@
controller.hideCurrentInputMethod()
}
- override fun updateBubbleBarLocation(location: BubbleBarLocation) {
- controller.bubbleBarLocation = location
+ override fun updateBubbleBarLocation(
+ location: BubbleBarLocation,
+ @BubbleBarLocation.UpdateSource source: Int,
+ ) {
+ controller.setBubbleBarLocation(location, source)
}
}
}
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 1855b93..9c2d3543 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,7 +44,7 @@
oneway void showUserEducation(in int positionX, in int positionY) = 8;
- oneway void setBubbleBarLocation(in BubbleBarLocation location) = 9;
+ oneway void setBubbleBarLocation(in BubbleBarLocation location, in int source) = 9;
oneway void updateBubbleBarTopOnScreen(in int topOnScreen) = 10;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index 272dfec..3764bcd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -637,11 +637,13 @@
return true;
}
if (action == R.id.action_move_bubble_bar_left) {
- mManager.updateBubbleBarLocation(BubbleBarLocation.LEFT);
+ mManager.updateBubbleBarLocation(BubbleBarLocation.LEFT,
+ BubbleBarLocation.UpdateSource.A11Y_ACTION_EXP_VIEW);
return true;
}
if (action == R.id.action_move_bubble_bar_right) {
- mManager.updateBubbleBarLocation(BubbleBarLocation.RIGHT);
+ mManager.updateBubbleBarLocation(BubbleBarLocation.RIGHT,
+ BubbleBarLocation.UpdateSource.A11Y_ACTION_EXP_VIEW);
return true;
}
return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 1f77abe..0c05e3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -441,7 +441,8 @@
@Override
public void onRelease(@NonNull BubbleBarLocation location) {
- mBubbleController.setBubbleBarLocation(location);
+ mBubbleController.setBubbleBarLocation(location,
+ BubbleBarLocation.UpdateSource.DRAG_EXP_VIEW);
if (location != mInitialLocation) {
BubbleLogger.Event event = location.isOnLeft(isLayoutRtl())
? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_EXP_VIEW
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 19a73f3..cc0e1df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -55,6 +55,7 @@
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_LAUNCHER;
+import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_MULTI_INSTANCE;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
@@ -1098,11 +1099,16 @@
void setSideStagePosition(@SplitPosition int sideStagePosition,
@Nullable WindowContainerTransaction wct) {
+ setSideStagePosition(sideStagePosition, true /* updateBounds */, wct);
+ }
+
+ private void setSideStagePosition(@SplitPosition int sideStagePosition, boolean updateBounds,
+ @Nullable WindowContainerTransaction wct) {
if (mSideStagePosition == sideStagePosition) return;
mSideStagePosition = sideStagePosition;
sendOnStagePositionChanged();
- if (mSideStage.mVisible) {
+ if (mSideStage.mVisible && updateBounds) {
if (wct == null) {
// onLayoutChanged builds/applies a wct with the contents of updateWindowBounds.
onLayoutSizeChanged(mSplitLayout);
@@ -1193,7 +1199,6 @@
if (!isSplitActive()) return;
final WindowContainerTransaction wct = new WindowContainerTransaction();
- setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
applyExitSplitScreen(childrenToTop, wct, exitReason);
}
@@ -1593,13 +1598,6 @@
}
if (present) {
updateRecentTasksSplitPair();
- } else if (mMainStage.getChildCount() == 0 && mSideStage.getChildCount() == 0) {
- mRecentTasks.ifPresent(recentTasks -> {
- // remove the split pair mapping from recentTasks, and disable further updates
- // to splits in the recents until we enter split again.
- recentTasks.removeSplitPair(taskId);
- });
- exitSplitScreen(mMainStage, EXIT_REASON_ROOT_TASK_VANISHED);
}
for (int i = mListeners.size() - 1; i >= 0; --i) {
diff --git a/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml
new file mode 100644
index 0000000..651e401
--- /dev/null
+++ b/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml
@@ -0,0 +1,19 @@
+<?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.
+ -->
+
+<resources>
+ <dimen name="keyguard_smartspace_top_offset">0dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/customization/res/values-sw600dp/dimens.xml b/packages/SystemUI/customization/res/values-sw600dp/dimens.xml
new file mode 100644
index 0000000..10e630d
--- /dev/null
+++ b/packages/SystemUI/customization/res/values-sw600dp/dimens.xml
@@ -0,0 +1,20 @@
+<?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.
+ -->
+
+<resources>
+ <!-- For portrait direction in unfold foldable device, we don't need keyguard_smartspace_top_offset-->
+ <dimen name="keyguard_smartspace_top_offset">0dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/customization/res/values/dimens.xml b/packages/SystemUI/customization/res/values/dimens.xml
index c574d1f..7feea6e 100644
--- a/packages/SystemUI/customization/res/values/dimens.xml
+++ b/packages/SystemUI/customization/res/values/dimens.xml
@@ -33,4 +33,10 @@
<dimen name="small_clock_height">114dp</dimen>
<dimen name="small_clock_padding_top">28dp</dimen>
<dimen name="clock_padding_start">28dp</dimen>
+
+ <!-- When large clock is showing, offset the smartspace by this amount -->
+ <dimen name="keyguard_smartspace_top_offset">12dp</dimen>
+ <!--Dimens used in both lockscreen preview and smartspace -->
+ <dimen name="date_weather_view_height">24dp</dimen>
+ <dimen name="enhanced_smartspace_height">104dp</dimen>
</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
index e142169..58fe2c9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
@@ -36,6 +36,7 @@
private const val USER_ID = 22
private const val OWNER_ID = 10
+private const val PASSWORD_ID = 30
private const val OPERATION_ID = 100L
private const val MAX_ATTEMPTS = 5
@@ -247,7 +248,11 @@
private fun pinRequest(credentialOwner: Int = USER_ID): BiometricPromptRequest.Credential.Pin =
BiometricPromptRequest.Credential.Pin(
promptInfo(),
- BiometricUserInfo(userId = USER_ID, deviceCredentialOwnerId = credentialOwner),
+ BiometricUserInfo(
+ userId = USER_ID,
+ deviceCredentialOwnerId = credentialOwner,
+ userIdForPasswordEntry = PASSWORD_ID,
+ ),
BiometricOperationInfo(OPERATION_ID),
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
index ecc62e9..87ab3c8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
@@ -69,7 +69,9 @@
get() =
kosmos.fakeSystemBarUtilsProxy.getStatusBarHeight() +
context.resources.getDimensionPixelSize(customR.dimen.small_clock_padding_top) +
- context.resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
+ context.resources.getDimensionPixelSize(
+ customR.dimen.keyguard_smartspace_top_offset
+ )
private val LARGE_CLOCK_TOP
get() =
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index 7d55169..89da465 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -13,11 +13,20 @@
*/
package com.android.systemui.plugins.clocks
+import android.content.Context
import android.graphics.Rect
import android.graphics.drawable.Drawable
+import android.util.DisplayMetrics
import android.view.View
import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
import com.android.internal.annotations.Keep
+import com.android.internal.policy.SystemBarUtils
import com.android.systemui.log.core.MessageBuffer
import com.android.systemui.plugins.Plugin
import com.android.systemui.plugins.annotations.GeneratedImport
@@ -149,7 +158,7 @@
@ProtectedReturn("return constraints;")
/** Custom constraints to apply to preview ConstraintLayout. */
- fun applyPreviewConstraints(constraints: ConstraintSet): ConstraintSet
+ fun applyPreviewConstraints(context: Context, constraints: ConstraintSet): ConstraintSet
fun applyAodBurnIn(aodBurnInModel: AodClockBurnInModel)
}
@@ -169,13 +178,84 @@
return constraints
}
- override fun applyPreviewConstraints(constraints: ConstraintSet): ConstraintSet {
- return constraints
+ override fun applyPreviewConstraints(
+ context: Context,
+ constraints: ConstraintSet,
+ ): ConstraintSet {
+ return applyDefaultPreviewConstraints(context, constraints)
}
override fun applyAodBurnIn(aodBurnInModel: AodClockBurnInModel) {
// Default clock doesn't need detailed control of view
}
+
+ companion object {
+ fun applyDefaultPreviewConstraints(
+ context: Context,
+ constraints: ConstraintSet,
+ ): ConstraintSet {
+ constraints.apply {
+ val lockscreenClockViewLargeId = getId(context, "lockscreen_clock_view_large")
+ constrainWidth(lockscreenClockViewLargeId, WRAP_CONTENT)
+ constrainHeight(lockscreenClockViewLargeId, WRAP_CONTENT)
+ constrainMaxHeight(lockscreenClockViewLargeId, 0)
+
+ val largeClockTopMargin =
+ SystemBarUtils.getStatusBarHeight(context) +
+ getDimen(context, "small_clock_padding_top") +
+ getDimen(context, "keyguard_smartspace_top_offset") +
+ getDimen(context, "date_weather_view_height") +
+ getDimen(context, "enhanced_smartspace_height")
+ connect(lockscreenClockViewLargeId, TOP, PARENT_ID, TOP, largeClockTopMargin)
+ connect(lockscreenClockViewLargeId, START, PARENT_ID, START)
+ connect(lockscreenClockViewLargeId, END, PARENT_ID, END)
+
+ // In preview, we'll show UDFPS icon for UDFPS devices
+ // and nothing for non-UDFPS devices,
+ // and we're not planning to add this vide in clockHostView
+ // so we only need position of device entry icon to constrain clock
+ // Copied calculation codes from applyConstraints in DefaultDeviceEntrySection
+ val bottomPaddingPx = getDimen(context, "lock_icon_margin_bottom")
+ val defaultDensity =
+ DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
+ DisplayMetrics.DENSITY_DEFAULT.toFloat()
+ val lockIconRadiusPx = (defaultDensity * 36).toInt()
+ val clockBottomMargin = bottomPaddingPx + 2 * lockIconRadiusPx
+
+ connect(lockscreenClockViewLargeId, BOTTOM, PARENT_ID, BOTTOM, clockBottomMargin)
+ val smallClockViewId = getId(context, "lockscreen_clock_view")
+ constrainWidth(smallClockViewId, WRAP_CONTENT)
+ constrainHeight(smallClockViewId, getDimen(context, "small_clock_height"))
+ connect(
+ smallClockViewId,
+ START,
+ PARENT_ID,
+ START,
+ getDimen(context, "clock_padding_start") +
+ getDimen(context, "status_view_margin_horizontal"),
+ )
+ val smallClockTopMargin =
+ getDimen(context, "keyguard_clock_top_margin") +
+ SystemBarUtils.getStatusBarHeight(context)
+ connect(smallClockViewId, TOP, PARENT_ID, TOP, smallClockTopMargin)
+ }
+ return constraints
+ }
+
+ fun getId(context: Context, name: String): Int {
+ val packageName = context.packageName
+ val res = context.packageManager.getResourcesForApplication(packageName)
+ val id = res.getIdentifier(name, "id", packageName)
+ return id
+ }
+
+ fun getDimen(context: Context, name: String): Int {
+ val packageName = context.packageName
+ val res = context.packageManager.getResourcesForApplication(packageName)
+ val id = res.getIdentifier(name, "dimen", packageName)
+ return if (id == 0) 0 else res.getDimensionPixelSize(id)
+ }
+ }
}
/** Events that should call when various rendering parameters change */
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index 2a27b47..4a53df9 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -24,7 +24,6 @@
<!-- margin from keyguard status bar to clock. For split shade it should be
keyguard_split_shade_top_margin - status_bar_header_height_keyguard = 8dp -->
<dimen name="keyguard_clock_top_margin">8dp</dimen>
- <dimen name="keyguard_smartspace_top_offset">0dp</dimen>
<!-- QS-->
<dimen name="qs_panel_padding_top">16dp</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 393631e..26f32ef 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -126,6 +126,4 @@
<dimen name="controls_content_padding">24dp</dimen>
<dimen name="control_list_vertical_spacing">8dp</dimen>
<dimen name="control_list_horizontal_spacing">16dp</dimen>
- <!-- For portrait direction in unfold foldable device, we don't need keyguard_smartspace_top_offset-->
- <dimen name="keyguard_smartspace_top_offset">0dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 7fa2879..67eb5b0 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -815,8 +815,7 @@
<dimen name="keyguard_clock_top_margin">18dp</dimen>
<!-- The amount to shift the clocks during a small/large transition -->
<dimen name="keyguard_clock_switch_y_shift">14dp</dimen>
- <!-- When large clock is showing, offset the smartspace by this amount -->
- <dimen name="keyguard_smartspace_top_offset">12dp</dimen>
+
<!-- The amount to translate lockscreen elements on the GONE->AOD transition -->
<dimen name="keyguard_enter_from_top_translation_y">-100dp</dimen>
<!-- The amount to translate lockscreen elements on the GONE->AOD transition, on device fold -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 11dde6a..71d4e9a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -147,7 +147,7 @@
mClockSwitchYAmount = mContext.getResources().getDimensionPixelSize(
R.dimen.keyguard_clock_switch_y_shift);
mSmartspaceTopOffset = (int) (mContext.getResources().getDimensionPixelSize(
- R.dimen.keyguard_smartspace_top_offset)
+ com.android.systemui.customization.R.dimen.keyguard_smartspace_top_offset)
* mContext.getResources().getConfiguration().fontScale
/ mContext.getResources().getDisplayMetrics().density
* SMARTSPACE_TOP_PADDING_MULTIPLIER);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
index b070068..08b3e99 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
@@ -72,7 +72,7 @@
// Request LockSettingsService to return the Gatekeeper Password in the
// VerifyCredentialResponse so that we can request a Gatekeeper HAT with the
// Gatekeeper Password and operationId.
- var effectiveUserId = request.userInfo.userIdForPasswordEntry
+ var effectiveUserId = request.userInfo.deviceCredentialOwnerId
val response =
if (Flags.privateSpaceBp() && effectiveUserId != request.userInfo.userId) {
effectiveUserId = request.userInfo.userId
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 46f5c05..914fdd2 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
@@ -18,34 +18,23 @@
package com.android.systemui.keyguard.ui.binder
import android.content.Context
-import android.util.DisplayMetrics
import android.view.View
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
-import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
-import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
-import androidx.constraintlayout.widget.ConstraintSet.START
-import androidx.constraintlayout.widget.ConstraintSet.TOP
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.internal.policy.SystemBarUtils
-import com.android.systemui.customization.R as customR
import com.android.systemui.keyguard.shared.model.ClockSizeSetting
import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRenderer
-import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection.Companion.getDimen
import com.android.systemui.keyguard.ui.view.layout.sections.setVisibility
import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.clocks.ClockController
-import com.android.systemui.res.R
import com.android.systemui.shared.clocks.ClockRegistry
-import com.android.systemui.util.Utils
import kotlin.reflect.KSuspendFunction1
/** Binder for the small clock view, large clock view. */
@@ -131,78 +120,6 @@
}
}
- private fun applyClockDefaultConstraints(context: Context, constraints: ConstraintSet) {
- constraints.apply {
- constrainWidth(customR.id.lockscreen_clock_view_large, ConstraintSet.WRAP_CONTENT)
- // The following two lines make lockscreen_clock_view_large is constrained to available
- // height when it goes beyond constraints; otherwise, it use WRAP_CONTENT
- constrainHeight(customR.id.lockscreen_clock_view_large, WRAP_CONTENT)
- constrainMaxHeight(customR.id.lockscreen_clock_view_large, 0)
- val largeClockTopMargin =
- SystemBarUtils.getStatusBarHeight(context) +
- context.resources.getDimensionPixelSize(customR.dimen.small_clock_padding_top) +
- context.resources.getDimensionPixelSize(
- R.dimen.keyguard_smartspace_top_offset
- ) +
- getDimen(context, DATE_WEATHER_VIEW_HEIGHT) +
- getDimen(context, ENHANCED_SMARTSPACE_HEIGHT)
- connect(
- customR.id.lockscreen_clock_view_large,
- TOP,
- PARENT_ID,
- TOP,
- largeClockTopMargin,
- )
- connect(customR.id.lockscreen_clock_view_large, START, PARENT_ID, START)
- connect(
- customR.id.lockscreen_clock_view_large,
- ConstraintSet.END,
- PARENT_ID,
- ConstraintSet.END,
- )
-
- // In preview, we'll show UDFPS icon for UDFPS devices and nothing for non-UDFPS
- // devices, but we need position of device entry icon to constrain clock
- if (getConstraint(lockId) != null) {
- connect(customR.id.lockscreen_clock_view_large, BOTTOM, lockId, TOP)
- } else {
- // Copied calculation codes from applyConstraints in DefaultDeviceEntrySection
- val bottomPaddingPx =
- context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom)
- val defaultDensity =
- DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
- DisplayMetrics.DENSITY_DEFAULT.toFloat()
- val lockIconRadiusPx = (defaultDensity * 36).toInt()
- val clockBottomMargin = bottomPaddingPx + 2 * lockIconRadiusPx
- connect(
- customR.id.lockscreen_clock_view_large,
- BOTTOM,
- PARENT_ID,
- BOTTOM,
- clockBottomMargin,
- )
- }
-
- constrainWidth(customR.id.lockscreen_clock_view, WRAP_CONTENT)
- constrainHeight(
- customR.id.lockscreen_clock_view,
- context.resources.getDimensionPixelSize(customR.dimen.small_clock_height),
- )
- connect(
- customR.id.lockscreen_clock_view,
- START,
- PARENT_ID,
- START,
- context.resources.getDimensionPixelSize(customR.dimen.clock_padding_start) +
- context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal),
- )
- val smallClockTopMargin =
- context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
- Utils.getStatusBarHeaderHeightKeyguard(context)
- connect(customR.id.lockscreen_clock_view, TOP, PARENT_ID, TOP, smallClockTopMargin)
- }
- }
-
private fun applyPreviewConstraints(
context: Context,
rootView: ConstraintLayout,
@@ -210,9 +127,8 @@
viewModel: KeyguardPreviewClockViewModel,
) {
val cs = ConstraintSet().apply { clone(rootView) }
- applyClockDefaultConstraints(context, cs)
- previewClock.largeClock.layout.applyPreviewConstraints(cs)
- previewClock.smallClock.layout.applyPreviewConstraints(cs)
+ previewClock.largeClock.layout.applyPreviewConstraints(context, cs)
+ previewClock.smallClock.layout.applyPreviewConstraints(context, cs)
// When selectedClockSize is the initial value, make both clocks invisible to avoid
// flickering
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index ee4f41d..6096cf7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -186,12 +186,23 @@
constraints.apply {
connect(customR.id.lockscreen_clock_view_large, START, PARENT_ID, START)
connect(customR.id.lockscreen_clock_view_large, END, guideline, END)
- connect(customR.id.lockscreen_clock_view_large, BOTTOM, R.id.device_entry_icon_view, TOP)
+ connect(
+ customR.id.lockscreen_clock_view_large,
+ BOTTOM,
+ R.id.device_entry_icon_view,
+ TOP,
+ )
val largeClockTopMargin =
keyguardClockViewModel.getLargeClockTopMargin() +
getDimen(DATE_WEATHER_VIEW_HEIGHT) +
getDimen(ENHANCED_SMARTSPACE_HEIGHT)
- connect(customR.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin)
+ connect(
+ customR.id.lockscreen_clock_view_large,
+ TOP,
+ PARENT_ID,
+ TOP,
+ largeClockTopMargin,
+ )
constrainWidth(customR.id.lockscreen_clock_view_large, WRAP_CONTENT)
// The following two lines make lockscreen_clock_view_large is constrained to available
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 5c79c0b..82adced 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
@@ -181,7 +181,7 @@
fun getLargeClockTopMargin(): Int {
return systemBarUtils.getStatusBarHeight() +
resources.getDimensionPixelSize(customR.dimen.small_clock_padding_top) +
- resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
+ resources.getDimensionPixelSize(customR.dimen.keyguard_smartspace_top_offset)
}
val largeClockTopMargin: Flow<Int> =
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 6579ea1..65c0f57 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
@@ -18,6 +18,7 @@
import android.content.Context
import com.android.internal.policy.SystemBarUtils
+import com.android.systemui.customization.R as customR
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.ClockSizeSetting
import com.android.systemui.res.R
@@ -39,20 +40,16 @@
val selectedClockSize: StateFlow<ClockSizeSetting> = interactor.selectedClockSize
val shouldHideSmartspace: Flow<Boolean> =
- combine(
- interactor.selectedClockSize,
- interactor.currentClockId,
- ::Pair,
- )
- .map { (size, currentClockId) ->
- when (size) {
- // TODO (b/284122375) This is temporary. We should use clockController
- // .largeClock.config.hasCustomWeatherDataDisplay instead, but
- // ClockRegistry.createCurrentClock is not reliable.
- ClockSizeSetting.DYNAMIC -> currentClockId == "DIGITAL_CLOCK_WEATHER"
- ClockSizeSetting.SMALL -> false
- }
+ combine(interactor.selectedClockSize, interactor.currentClockId, ::Pair).map {
+ (size, currentClockId) ->
+ when (size) {
+ // TODO (b/284122375) This is temporary. We should use clockController
+ // .largeClock.config.hasCustomWeatherDataDisplay instead, but
+ // ClockRegistry.createCurrentClock is not reliable.
+ ClockSizeSetting.DYNAMIC -> currentClockId == "DIGITAL_CLOCK_WEATHER"
+ ClockSizeSetting.SMALL -> false
}
+ }
fun getSmartspaceStartPadding(context: Context): Int {
return KeyguardSmartspaceViewModel.getSmartspaceStartMargin(context)
@@ -83,7 +80,7 @@
} else {
getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
SystemBarUtils.getStatusBarHeight(context) +
- getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
+ getDimensionPixelSize(customR.dimen.keyguard_smartspace_top_offset)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index 850e943..ef6ae0d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -17,8 +17,10 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.content.res.Resources
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.customization.R as customR
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
@@ -42,7 +44,6 @@
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
class LockscreenContentViewModel
@AssistedInject
@@ -82,10 +83,7 @@
unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = true),
unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = false),
) { start, end ->
- UnfoldTranslations(
- start = start,
- end = end,
- )
+ UnfoldTranslations(start = start, end = end)
}
.collect { _unfoldTranslations.value = it }
}
@@ -102,17 +100,15 @@
/** Returns a flow that indicates whether lockscreen notifications should be rendered. */
fun areNotificationsVisible(): Flow<Boolean> {
- return combine(
- clockSize,
- shadeInteractor.isShadeLayoutWide,
- ) { clockSize, isShadeLayoutWide ->
+ return combine(clockSize, shadeInteractor.isShadeLayoutWide) { clockSize, isShadeLayoutWide
+ ->
clockSize == ClockSize.SMALL || isShadeLayoutWide
}
}
fun getSmartSpacePaddingTop(resources: Resources): Int {
return if (clockSize.value == ClockSize.LARGE) {
- resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) +
+ resources.getDimensionPixelSize(customR.dimen.keyguard_smartspace_top_offset) +
resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin)
} else {
0
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt
index 9580016..1f8d365 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt
@@ -102,6 +102,10 @@
return --lives <= 0
}
}
+
+ override fun toString(): String {
+ return "$key = $value"
+ }
}
/**
@@ -174,7 +178,10 @@
pw.println("$TAG(retainCount = $retainCount, purgeTimeoutMillis = $purgeTimeoutMillis)")
pw.withIncreasedIndent {
- pw.printCollection("keys present in cache", cache.keys.stream().sorted().toList())
+ pw.printCollection(
+ "entries present in cache",
+ cache.values.stream().map { it.toString() }.sorted().toList(),
+ )
val misses = misses.get()
val hits = hits.get()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 48106de..fc318d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -2395,7 +2395,8 @@
FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
mBubbleController.registerBubbleStateListener(bubbleStateListener);
- mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT);
+ mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT,
+ BubbleBarLocation.UpdateSource.DRAG_EXP_VIEW);
assertThat(bubbleStateListener.mLastUpdate).isNotNull();
assertThat(bubbleStateListener.mLastUpdate.bubbleBarLocation).isEqualTo(
BubbleBarLocation.LEFT);
@@ -2408,7 +2409,8 @@
FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
mBubbleController.registerBubbleStateListener(bubbleStateListener);
- mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT);
+ mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT,
+ BubbleBarLocation.UpdateSource.DRAG_EXP_VIEW);
assertThat(bubbleStateListener.mStateChangeCalls).isEqualTo(0);
}
@@ -2535,6 +2537,78 @@
@EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
@Test
+ public void testEventLogging_bubbleBar_dragBarLeft() {
+ mBubbleProperties.mIsBubbleBarEnabled = true;
+ mPositioner.setIsLargeScreen(true);
+ mPositioner.setBubbleBarLocation(BubbleBarLocation.RIGHT);
+ FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+ mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+ mEntryListener.onEntryAdded(mRow);
+ assertBarMode();
+
+ mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT,
+ BubbleBarLocation.UpdateSource.DRAG_BAR);
+
+ verify(mBubbleLogger).log(BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BAR);
+ }
+
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+ @Test
+ public void testEventLogging_bubbleBar_dragBarRight() {
+ mBubbleProperties.mIsBubbleBarEnabled = true;
+ mPositioner.setIsLargeScreen(true);
+ mPositioner.setBubbleBarLocation(BubbleBarLocation.LEFT);
+ FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+ mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+ mEntryListener.onEntryAdded(mRow);
+ assertBarMode();
+
+ mBubbleController.setBubbleBarLocation(BubbleBarLocation.RIGHT,
+ BubbleBarLocation.UpdateSource.DRAG_BAR);
+
+ verify(mBubbleLogger).log(BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BAR);
+ }
+
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+ @Test
+ public void testEventLogging_bubbleBar_dragBubbleLeft() {
+ mBubbleProperties.mIsBubbleBarEnabled = true;
+ mPositioner.setIsLargeScreen(true);
+ mPositioner.setBubbleBarLocation(BubbleBarLocation.RIGHT);
+ FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+ mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+ mEntryListener.onEntryAdded(mRow);
+ assertBarMode();
+
+ mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT,
+ BubbleBarLocation.UpdateSource.DRAG_BUBBLE);
+
+ verify(mBubbleLogger).log(BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BUBBLE);
+ }
+
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+ @Test
+ public void testEventLogging_bubbleBar_dragBubbleRight() {
+ mBubbleProperties.mIsBubbleBarEnabled = true;
+ mPositioner.setIsLargeScreen(true);
+ mPositioner.setBubbleBarLocation(BubbleBarLocation.LEFT);
+ FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+ mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+ mEntryListener.onEntryAdded(mRow);
+ assertBarMode();
+
+ mBubbleController.setBubbleBarLocation(BubbleBarLocation.RIGHT,
+ BubbleBarLocation.UpdateSource.DRAG_BUBBLE);
+
+ verify(mBubbleLogger).log(BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BUBBLE);
+ }
+
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+ @Test
public void testEventLogging_bubbleBar_expandAndCollapse() {
mBubbleProperties.mIsBubbleBarEnabled = true;
mPositioner.setIsLargeScreen(true);
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index 23cee9d..1588e04 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -53,6 +53,7 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
@@ -400,9 +401,18 @@
Slog.w(TAG, "Tombstone too large to add to DropBox: " + tombstone.toPath());
return;
}
- // Read the proto tombstone file as bytes.
- final byte[] tombstoneBytes = Files.readAllBytes(tombstone.toPath());
+ // Read the proto tombstone file as bytes.
+ // Previously used Files.readAllBytes() which internally creates a ThreadLocal BufferCache
+ // via ChannelInputStream that isn't properly released. Switched to
+ // FileInputStream.transferTo() which avoids the NIO channels completely,
+ // preventing the memory leak while maintaining the same functionality.
+ final byte[] tombstoneBytes;
+ try (FileInputStream fis = new FileInputStream(tombstone);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ fis.transferTo(baos);
+ tombstoneBytes = baos.toByteArray();
+ }
final File tombstoneProtoWithHeaders = File.createTempFile(
tombstone.getName(), ".tmp", TOMBSTONE_TMP_DIR);
Files.setPosixFilePermissions(
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 116aeea..38df10a 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -44,7 +44,6 @@
import android.app.BroadcastOptions.DeliveryGroupPolicy;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
-import android.compat.annotation.Overridable;
import android.content.ComponentName;
import android.content.IIntentReceiver;
import android.content.Intent;
@@ -88,7 +87,6 @@
*/
@ChangeId
@EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.BASE)
- @Overridable
@VisibleForTesting
static final long CHANGE_LIMIT_PRIORITY_SCOPE = 371307720L;
diff --git a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
index f4a931f..d2af84c 100644
--- a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
+++ b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
@@ -19,7 +19,6 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
import static com.android.server.am.ActivityManagerService.checkComponentPermission;
import static com.android.server.am.BroadcastQueue.TAG;
-import static com.android.server.am.Flags.usePermissionManagerForBroadcastDeliveryCheck;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -289,33 +288,16 @@
if (info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID &&
r.requiredPermissions != null && r.requiredPermissions.length > 0) {
- final AttributionSource[] attributionSources;
- if (usePermissionManagerForBroadcastDeliveryCheck()) {
- attributionSources = createAttributionSourcesForResolveInfo(info);
- } else {
- attributionSources = null;
- }
+ final AttributionSource[] attributionSources =
+ createAttributionSourcesForResolveInfo(info);
for (int i = 0; i < r.requiredPermissions.length; i++) {
String requiredPermission = r.requiredPermissions[i];
- try {
- if (usePermissionManagerForBroadcastDeliveryCheck()) {
- perm = hasPermissionForDataDelivery(
- requiredPermission,
- "Broadcast delivered to " + info.activityInfo.name,
- attributionSources)
- ? PackageManager.PERMISSION_GRANTED
- : PackageManager.PERMISSION_DENIED;
- } else {
- perm = AppGlobals.getPackageManager()
- .checkPermission(
- requiredPermission,
- info.activityInfo.applicationInfo.packageName,
- UserHandle
- .getUserId(info.activityInfo.applicationInfo.uid));
- }
- } catch (RemoteException e) {
- perm = PackageManager.PERMISSION_DENIED;
- }
+ perm = hasPermissionForDataDelivery(
+ requiredPermission,
+ "Broadcast delivered to " + info.activityInfo.name,
+ attributionSources)
+ ? PackageManager.PERMISSION_GRANTED
+ : PackageManager.PERMISSION_DENIED;
if (perm != PackageManager.PERMISSION_GRANTED) {
return "Permission Denial: receiving "
+ r.intent + " to "
@@ -324,15 +306,6 @@
+ " due to sender " + r.callerPackage
+ " (uid " + r.callingUid + ")";
}
- if (!usePermissionManagerForBroadcastDeliveryCheck()) {
- int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
- if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp) {
- if (!noteOpForManifestReceiver(appOp, r, info, component)) {
- return "Skipping delivery to " + info.activityInfo.packageName
- + " due to required appop " + appOp;
- }
- }
- }
}
}
if (r.appOp != AppOpsManager.OP_NONE) {
@@ -452,35 +425,20 @@
// Check that the receiver has the required permission(s) to receive this broadcast.
if (r.requiredPermissions != null && r.requiredPermissions.length > 0) {
- final AttributionSource attributionSource;
- if (usePermissionManagerForBroadcastDeliveryCheck()) {
- attributionSource =
- new AttributionSource.Builder(filter.receiverList.uid)
- .setPid(filter.receiverList.pid)
- .setPackageName(filter.packageName)
- .setAttributionTag(filter.featureId)
- .build();
- } else {
- attributionSource = null;
- }
+ final AttributionSource attributionSource =
+ new AttributionSource.Builder(filter.receiverList.uid)
+ .setPid(filter.receiverList.pid)
+ .setPackageName(filter.packageName)
+ .setAttributionTag(filter.featureId)
+ .build();
for (int i = 0; i < r.requiredPermissions.length; i++) {
String requiredPermission = r.requiredPermissions[i];
- final int perm;
- if (usePermissionManagerForBroadcastDeliveryCheck()) {
- perm = hasPermissionForDataDelivery(
- requiredPermission,
- "Broadcast delivered to registered receiver " + filter.receiverId,
- attributionSource)
- ? PackageManager.PERMISSION_GRANTED
- : PackageManager.PERMISSION_DENIED;
- } else {
- perm = checkComponentPermission(
- requiredPermission,
- filter.receiverList.pid,
- filter.receiverList.uid,
- -1 /* owningUid */,
- true /* exported */);
- }
+ final int perm = hasPermissionForDataDelivery(
+ requiredPermission,
+ "Broadcast delivered to registered receiver " + filter.receiverId,
+ attributionSource)
+ ? PackageManager.PERMISSION_GRANTED
+ : PackageManager.PERMISSION_DENIED;
if (perm != PackageManager.PERMISSION_GRANTED) {
return "Permission Denial: receiving "
+ r.intent.toString()
@@ -491,24 +449,6 @@
+ " due to sender " + r.callerPackage
+ " (uid " + r.callingUid + ")";
}
- if (!usePermissionManagerForBroadcastDeliveryCheck()) {
- int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
- if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp
- && mService.getAppOpsManager().noteOpNoThrow(appOp,
- filter.receiverList.uid, filter.packageName, filter.featureId,
- "Broadcast delivered to registered receiver " + filter.receiverId)
- != AppOpsManager.MODE_ALLOWED) {
- return "Appop Denial: receiving "
- + r.intent.toString()
- + " to " + filter.receiverList.app
- + " (pid=" + filter.receiverList.pid
- + ", uid=" + filter.receiverList.uid + ")"
- + " requires appop " + AppOpsManager.permissionToOp(
- requiredPermission)
- + " due to sender " + r.callerPackage
- + " (uid " + r.callingUid + ")";
- }
- }
}
}
if ((r.requiredPermissions == null || r.requiredPermissions.length == 0)) {
diff --git a/services/core/java/com/android/server/pm/InstallDependencyHelper.java b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
new file mode 100644
index 0000000..745665b
--- /dev/null
+++ b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
@@ -0,0 +1,67 @@
+/*
+ * 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.server.pm;
+
+import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
+
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.parsing.PackageLite;
+import android.os.OutcomeReceiver;
+
+import java.util.List;
+
+/**
+ * Helper class to interact with SDK Dependency Installer service.
+ */
+public class InstallDependencyHelper {
+ private final SharedLibrariesImpl mSharedLibraries;
+
+ InstallDependencyHelper(SharedLibrariesImpl sharedLibraries) {
+ mSharedLibraries = sharedLibraries;
+ }
+
+ void resolveLibraryDependenciesIfNeeded(PackageLite pkg,
+ OutcomeReceiver<Void, PackageManagerException> callback) {
+ final List<SharedLibraryInfo> missing;
+ try {
+ missing = mSharedLibraries.collectMissingSharedLibraryInfos(pkg);
+ } catch (PackageManagerException e) {
+ callback.onError(e);
+ return;
+ }
+
+ if (missing.isEmpty()) {
+ // No need for dependency resolution. Move to installation directly.
+ callback.onResult(null);
+ return;
+ }
+
+ try {
+ bindToDependencyInstaller();
+ } catch (Exception e) {
+ PackageManagerException pe = new PackageManagerException(
+ INSTALL_FAILED_MISSING_SHARED_LIBRARY, e.getMessage());
+ callback.onError(pe);
+ }
+ }
+
+ private void bindToDependencyInstaller() {
+ throw new IllegalStateException("Failed to bind to Dependency Installer");
+ }
+
+
+}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index ef09976..eb70748 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -220,6 +220,7 @@
private AppOpsManager mAppOps;
private final VerifierController mVerifierController;
+ private final InstallDependencyHelper mInstallDependencyHelper;
private final HandlerThread mInstallThread;
private final Handler mInstallHandler;
@@ -346,6 +347,8 @@
synchronized (mVerificationPolicyPerUser) {
mVerificationPolicyPerUser.put(USER_SYSTEM, DEFAULT_VERIFICATION_POLICY);
}
+ mInstallDependencyHelper = new InstallDependencyHelper(
+ mPm.mInjector.getSharedLibrariesImpl());
LocalServices.getService(SystemServiceManager.class).startService(
new Lifecycle(context, this));
@@ -543,7 +546,7 @@
session = PackageInstallerSession.readFromXml(in, mInternalCallback,
mContext, mPm, mInstallThread.getLooper(), mStagingManager,
mSessionsDir, this, mSilentUpdatePolicy,
- mVerifierController);
+ mVerifierController, mInstallDependencyHelper);
} catch (Exception e) {
Slog.e(TAG, "Could not read session", e);
continue;
@@ -1065,7 +1068,8 @@
userId, callingUid, installSource, params, createdMillis, 0L, stageDir, stageCid,
null, null, false, false, false, false, null, SessionInfo.INVALID_ID,
false, false, false, PackageManager.INSTALL_UNKNOWN, "", null,
- mVerifierController, verificationPolicy, verificationPolicy);
+ mVerifierController, verificationPolicy, verificationPolicy,
+ mInstallDependencyHelper);
synchronized (mSessions) {
mSessions.put(sessionId, session);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 2a92de5..bad1201 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -145,6 +145,7 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.OutcomeReceiver;
import android.os.ParcelFileDescriptor;
import android.os.ParcelableException;
import android.os.PersistableBundle;
@@ -433,6 +434,8 @@
private final StagingManager mStagingManager;
@NonNull private final VerifierController mVerifierController;
+ private final InstallDependencyHelper mInstallDependencyHelper;
+
final int sessionId;
final int userId;
final SessionParams params;
@@ -1188,7 +1191,8 @@
String sessionErrorMessage, DomainSet preVerifiedDomains,
@NonNull VerifierController verifierController,
@PackageInstaller.VerificationPolicy int initialVerificationPolicy,
- @PackageInstaller.VerificationPolicy int currentVerificationPolicy) {
+ @PackageInstaller.VerificationPolicy int currentVerificationPolicy,
+ InstallDependencyHelper installDependencyHelper) {
mCallback = callback;
mContext = context;
mPm = pm;
@@ -1200,6 +1204,7 @@
mVerifierController = verifierController;
mInitialVerificationPolicy = initialVerificationPolicy;
mCurrentVerificationPolicy = new AtomicInteger(currentVerificationPolicy);
+ mInstallDependencyHelper = installDependencyHelper;
this.sessionId = sessionId;
this.userId = userId;
@@ -2611,6 +2616,13 @@
maybeFinishChildSessions(error, msg);
}
+ private void onSessionDependencyResolveFailure(int error, String msg) {
+ Slog.e(TAG, "Failed to resolve dependency for session " + sessionId);
+ // Dispatch message to remove session from PackageInstallerService.
+ dispatchSessionFinished(error, msg, null);
+ maybeFinishChildSessions(error, msg);
+ }
+
private void onSystemDataLoaderUnrecoverable() {
final String packageName = getPackageName();
if (TextUtils.isEmpty(packageName)) {
@@ -3402,7 +3414,34 @@
/* extras= */ null, /* forPreapproval= */ false);
return;
}
- install();
+
+ if (Flags.sdkDependencyInstaller() && !isMultiPackage()) {
+ resolveLibraryDependenciesIfNeeded();
+ } else {
+ install();
+ }
+ }
+
+
+ private void resolveLibraryDependenciesIfNeeded() {
+ synchronized (mLock) {
+ // TODO(b/372862145): Callback should be called on a handler passed as parameter
+ mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(mPackageLite,
+ new OutcomeReceiver<>() {
+
+ @Override
+ public void onResult(Void result) {
+ install();
+ }
+
+ @Override
+ public void onError(@NonNull PackageManagerException e) {
+ final String completeMsg = ExceptionUtils.getCompleteMessage(e);
+ setSessionFailed(e.error, completeMsg);
+ onSessionDependencyResolveFailure(e.error, completeMsg);
+ }
+ });
+ }
}
/**
@@ -6048,7 +6087,8 @@
@NonNull StagingManager stagingManager, @NonNull File sessionsDir,
@NonNull PackageSessionProvider sessionProvider,
@NonNull SilentUpdatePolicy silentUpdatePolicy,
- @NonNull VerifierController verifierController)
+ @NonNull VerifierController verifierController,
+ @NonNull InstallDependencyHelper installDependencyHelper)
throws IOException, XmlPullParserException {
final int sessionId = in.getAttributeInt(null, ATTR_SESSION_ID);
final int userId = in.getAttributeInt(null, ATTR_USER_ID);
@@ -6257,6 +6297,6 @@
stageCid, fileArray, checksumsMap, prepared, committed, destroyed, sealed,
childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied,
sessionErrorCode, sessionErrorMessage, preVerifiedDomains, verifierController,
- initialVerificationPolicy, currentVerificationPolicy);
+ initialVerificationPolicy, currentVerificationPolicy, installDependencyHelper);
}
}
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index 929fccc..fc54f68 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -33,6 +33,7 @@
import android.content.pm.Signature;
import android.content.pm.SigningDetails;
import android.content.pm.VersionedPackage;
+import android.content.pm.parsing.PackageLite;
import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
@@ -83,6 +84,7 @@
private static final boolean DEBUG_SHARED_LIBRARIES = false;
private static final String LIBRARY_TYPE_SDK = "sdk";
+ private static final String LIBRARY_TYPE_STATIC = "static shared";
/**
* Apps targeting Android S and above need to declare dependencies to the public native
@@ -926,18 +928,19 @@
if (!pkg.getUsesLibraries().isEmpty()) {
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesLibraries(), null, null, null,
pkg.getPackageName(), "shared", true, pkg.getTargetSdkVersion(), null,
- availablePackages, newLibraries);
+ availablePackages, newLibraries, null);
}
if (!pkg.getUsesStaticLibraries().isEmpty()) {
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesStaticLibraries(),
pkg.getUsesStaticLibrariesVersions(), pkg.getUsesStaticLibrariesCertDigests(),
- null, pkg.getPackageName(), "static shared", true,
- pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, newLibraries);
+ null, pkg.getPackageName(), LIBRARY_TYPE_STATIC, true,
+ pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, newLibraries,
+ null);
}
if (!pkg.getUsesOptionalLibraries().isEmpty()) {
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalLibraries(), null, null,
null, pkg.getPackageName(), "shared", false, pkg.getTargetSdkVersion(),
- usesLibraryInfos, availablePackages, newLibraries);
+ usesLibraryInfos, availablePackages, newLibraries, null);
}
if (platformCompat.isChangeEnabledInternal(ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES,
pkg.getPackageName(), pkg.getTargetSdkVersion())) {
@@ -945,13 +948,13 @@
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesNativeLibraries(), null,
null, null, pkg.getPackageName(), "native shared", true,
pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages,
- newLibraries);
+ newLibraries, null);
}
if (!pkg.getUsesOptionalNativeLibraries().isEmpty()) {
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalNativeLibraries(),
null, null, null, pkg.getPackageName(), "native shared", false,
pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages,
- newLibraries);
+ newLibraries, null);
}
}
if (!pkg.getUsesSdkLibraries().isEmpty()) {
@@ -961,11 +964,34 @@
pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesCertDigests(),
pkg.getUsesSdkLibrariesOptional(),
pkg.getPackageName(), LIBRARY_TYPE_SDK, required, pkg.getTargetSdkVersion(),
- usesLibraryInfos, availablePackages, newLibraries);
+ usesLibraryInfos, availablePackages, newLibraries, null);
}
return usesLibraryInfos;
}
+ List<SharedLibraryInfo> collectMissingSharedLibraryInfos(PackageLite pkgLite)
+ throws PackageManagerException {
+ ArrayList<SharedLibraryInfo> missingSharedLibrary = new ArrayList<>();
+ synchronized (mPm.mLock) {
+ collectSharedLibraryInfos(pkgLite.getUsesSdkLibraries(),
+ pkgLite.getUsesSdkLibrariesVersionsMajor(),
+ pkgLite.getUsesSdkLibrariesCertDigests(),
+ /*libsOptional=*/ null, pkgLite.getPackageName(), LIBRARY_TYPE_SDK,
+ /*required=*/ true, pkgLite.getTargetSdk(),
+ /*outUsedLibraries=*/ null, mPm.mPackages, /*newLibraries=*/ null,
+ missingSharedLibrary);
+
+ collectSharedLibraryInfos(pkgLite.getUsesStaticLibraries(),
+ pkgLite.getUsesStaticLibrariesVersions(),
+ pkgLite.getUsesStaticLibrariesCertDigests(),
+ /*libsOptional=*/ null, pkgLite.getPackageName(), LIBRARY_TYPE_STATIC,
+ /*required=*/ true, pkgLite.getTargetSdk(),
+ /*outUsedLibraries=*/ null, mPm.mPackages, /*newLibraries=*/ null,
+ missingSharedLibrary);
+ }
+ return missingSharedLibrary;
+ }
+
private ArrayList<SharedLibraryInfo> collectSharedLibraryInfos(
@NonNull List<String> requestedLibraries,
@Nullable long[] requiredVersions, @Nullable String[][] requiredCertDigests,
@@ -973,7 +999,8 @@
@NonNull String packageName, @NonNull String libraryType, boolean required,
int targetSdk, @Nullable ArrayList<SharedLibraryInfo> outUsedLibraries,
@NonNull final Map<String, AndroidPackage> availablePackages,
- @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries)
+ @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries,
+ @Nullable final List<SharedLibraryInfo> outMissingSharedLibraryInfos)
throws PackageManagerException {
final int libCount = requestedLibraries.size();
for (int i = 0; i < libCount; i++) {
@@ -986,16 +1013,33 @@
libName, libVersion, mSharedLibraries, newLibraries);
}
if (libraryInfo == null) {
- // Only allow app be installed if the app specifies the sdk-library dependency is
- // optional
- if (required || (LIBRARY_TYPE_SDK.equals(libraryType) && (libsOptional != null
- && !libsOptional[i]))) {
- throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
- "Package " + packageName + " requires unavailable " + libraryType
- + " library " + libName + "; failing!");
- } else if (DEBUG_SHARED_LIBRARIES) {
- Slog.i(TAG, "Package " + packageName + " desires unavailable " + libraryType
- + " library " + libName + "; ignoring!");
+ if (required) {
+ boolean isSdkOrStatic = libraryType.equals(LIBRARY_TYPE_SDK)
+ || libraryType.equals(LIBRARY_TYPE_STATIC);
+ if (isSdkOrStatic && outMissingSharedLibraryInfos != null) {
+ // TODO(b/372862145): Pass the CertDigest too
+ // If Dependency Installation is supported, try that instead of failing.
+ SharedLibraryInfo missingLibrary = new SharedLibraryInfo(
+ libName, libVersion, SharedLibraryInfo.TYPE_SDK_PACKAGE
+ );
+ outMissingSharedLibraryInfos.add(missingLibrary);
+ } else {
+ throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+ "Package " + packageName + " requires unavailable " + libraryType
+ + " library " + libName + "; failing!");
+ }
+ } else {
+ // Only allow app be installed if the app specifies the sdk-library
+ // dependency is optional
+ boolean isOptional = libsOptional != null && libsOptional[i];
+ if (LIBRARY_TYPE_SDK.equals(libraryType) && !isOptional) {
+ throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+ "Package " + packageName + " requires unavailable " + libraryType
+ + " library " + libName + "; failing!");
+ } else if (DEBUG_SHARED_LIBRARIES) {
+ Slog.i(TAG, "Package " + packageName + " desires unavailable " + libraryType
+ + " library " + libName + "; ignoring!");
+ }
}
} else {
if (requiredVersions != null && requiredCertDigests != null) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 9759772..19b0343 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1621,7 +1621,8 @@
mSystemServiceManager.startService(ROLE_SERVICE_CLASS);
t.traceEnd();
- if (!isWatch && android.app.supervision.flags.Flags.supervisionApi()) {
+ if (android.app.supervision.flags.Flags.supervisionApi()
+ && (!isWatch || android.app.supervision.flags.Flags.supervisionApiOnWear())) {
t.traceBegin("StartSupervisionService");
mSystemServiceManager.startService(SupervisionService.Lifecycle.class);
t.traceEnd();
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
index 09d0e4a..5a59c57 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
@@ -201,7 +201,8 @@
/* preVerifiedDomains */ DomainSet(setOf("com.foo", "com.bar")),
/* VerifierController */ mock(VerifierController::class.java),
/* initialVerificationPolicy */ VERIFICATION_POLICY_BLOCK_FAIL_OPEN,
- /* currentVerificationPolicy */ VERIFICATION_POLICY_BLOCK_FAIL_CLOSED
+ /* currentVerificationPolicy */ VERIFICATION_POLICY_BLOCK_FAIL_CLOSED,
+ /* installDependencyHelper */ null
)
}
@@ -256,7 +257,8 @@
mTmpDir,
mock(PackageSessionProvider::class.java),
mock(SilentUpdatePolicy::class.java),
- mock(VerifierController::class.java)
+ mock(VerifierController::class.java),
+ mock(InstallDependencyHelper::class.java)
)
ret.add(session)
} catch (e: Exception) {
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 979384c6..6acf242 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -109,6 +109,10 @@
optimize: {
enabled: false,
},
+
+ data: [
+ ":HelloWorldUsingSdk1And2",
+ ],
}
java_library {
diff --git a/services/tests/mockingservicestests/AndroidTest.xml b/services/tests/mockingservicestests/AndroidTest.xml
index 7782d57..2b90119 100644
--- a/services/tests/mockingservicestests/AndroidTest.xml
+++ b/services/tests/mockingservicestests/AndroidTest.xml
@@ -23,6 +23,12 @@
<option name="test-file-name" value="FrameworksMockingServicesTests.apk" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true"/>
+ <option name="push-file" key="HelloWorldUsingSdk1And2.apk"
+ value="/data/local/tmp/tests/smockingservicestest/pm/HelloWorldUsingSdk1And2.apk"/>
+ </target_preparer>
+
<option name="test-tag" value="FrameworksMockingServicesTests" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
new file mode 100644
index 0000000..f6c644e
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
@@ -0,0 +1,192 @@
+/*
+ * 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.server.pm;
+
+import static android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.parsing.ApkLite;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
+import android.os.FileUtils;
+import android.os.OutcomeReceiver;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@Presubmit
+@RunWith(JUnit4.class)
+@RequiresFlagsEnabled(FLAG_SDK_DEPENDENCY_INSTALLER)
+public class InstallDependencyHelperTest {
+
+ @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
+ @Rule public final CheckFlagsRule checkFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ private static final String PUSH_FILE_DIR = "/data/local/tmp/tests/smockingservicestest/pm/";
+ private static final String TEST_APP_USING_SDK1_AND_SDK2 = "HelloWorldUsingSdk1And2.apk";
+
+ @Mock private SharedLibrariesImpl mSharedLibraries;
+ private InstallDependencyHelper mInstallDependencyHelper;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mInstallDependencyHelper = new InstallDependencyHelper(mSharedLibraries);
+ }
+
+ @Test
+ public void testResolveLibraryDependenciesIfNeeded_errorInSharedLibrariesImpl()
+ throws Exception {
+ doThrow(new PackageManagerException(new Exception("xyz")))
+ .when(mSharedLibraries).collectMissingSharedLibraryInfos(any());
+
+ PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2);
+ CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ false);
+ mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, callback);
+ callback.assertFailure();
+
+ assertThat(callback.error).hasMessageThat().contains("xyz");
+ }
+
+ @Test
+ public void testResolveLibraryDependenciesIfNeeded_failsToBind() throws Exception {
+ // Return a non-empty list as missing dependency
+ PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2);
+ List<SharedLibraryInfo> missingDependency = Collections.singletonList(
+ mock(SharedLibraryInfo.class));
+ when(mSharedLibraries.collectMissingSharedLibraryInfos(eq(pkg)))
+ .thenReturn(missingDependency);
+
+ CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ false);
+ mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, callback);
+ callback.assertFailure();
+
+ assertThat(callback.error).hasMessageThat().contains(
+ "Failed to bind to Dependency Installer");
+ }
+
+
+ @Test
+ public void testResolveLibraryDependenciesIfNeeded_allDependenciesInstalled() throws Exception {
+ // Return an empty list as missing dependency
+ PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2);
+ List<SharedLibraryInfo> missingDependency = Collections.emptyList();
+ when(mSharedLibraries.collectMissingSharedLibraryInfos(eq(pkg)))
+ .thenReturn(missingDependency);
+
+ CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ true);
+ mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, callback);
+ callback.assertSuccess();
+ }
+
+ private static class CallbackHelper implements OutcomeReceiver<Void, PackageManagerException> {
+ public PackageManagerException error;
+
+ private final CountDownLatch mWait = new CountDownLatch(1);
+ private final boolean mExpectSuccess;
+
+ CallbackHelper(boolean expectSuccess) {
+ mExpectSuccess = expectSuccess;
+ }
+
+ @Override
+ public void onResult(Void result) {
+ if (!mExpectSuccess) {
+ fail("Expected to fail");
+ }
+ mWait.countDown();
+ }
+
+ @Override
+ public void onError(@NonNull PackageManagerException e) {
+ if (mExpectSuccess) {
+ fail("Expected success but received: " + e);
+ }
+ error = e;
+ mWait.countDown();
+ }
+
+ void assertSuccess() throws Exception {
+ assertThat(mWait.await(1000, TimeUnit.MILLISECONDS)).isTrue();
+ assertThat(error).isNull();
+ }
+
+ void assertFailure() throws Exception {
+ assertThat(mWait.await(1000, TimeUnit.MILLISECONDS)).isTrue();
+ assertThat(error).isNotNull();
+ }
+
+ }
+
+ private PackageLite getPackageLite(String apkFileName) throws Exception {
+ File apkFile = copyApkToTmpDir(TEST_APP_USING_SDK1_AND_SDK2);
+ ParseResult<ApkLite> result = ApkLiteParseUtils.parseApkLite(
+ ParseTypeImpl.forDefaultParsing().reset(), apkFile, 0);
+ assertThat(result.isError()).isFalse();
+ ApkLite baseApk = result.getResult();
+
+ return new PackageLite(/*path=*/ null, baseApk.getPath(), baseApk,
+ /*splitNames=*/ null, /*isFeatureSplits=*/ null, /*usesSplitNames=*/ null,
+ /*configForSplit=*/ null, /*splitApkPaths=*/ null,
+ /*splitRevisionCodes=*/ null, baseApk.getTargetSdkVersion(),
+ /*requiredSplitTypes=*/ null, /*splitTypes=*/ null);
+ }
+
+ private File copyApkToTmpDir(String apkFileName) throws Exception {
+ File outFile = temporaryFolder.newFile(apkFileName);
+ String apkFilePath = PUSH_FILE_DIR + apkFileName;
+ File apkFile = new File(apkFilePath);
+ assertThat(apkFile.exists()).isTrue();
+ try (InputStream is = new FileInputStream(apkFile)) {
+ FileUtils.copyToFileOrThrow(is, outFile);
+ }
+ return outFile;
+ }
+
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
index 591e8df..71c60ad 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
@@ -742,10 +742,11 @@
/* stagedSessionErrorMessage */ "no error",
/* preVerifiedDomains */ null,
/* verifierController */ null,
- /* initialVerificationPolicy */
+ /* initialVerificationPolicy */
PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_CLOSED,
/* currentVerificationPolicy */
- PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_CLOSED);
+ PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_CLOSED,
+ /* installDependencyHelper */ null);
StagingManager.StagedSession stagedSession = spy(session.mStagedSession);
doReturn(packageName).when(stagedSession).getPackageName();
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index ac96ef2..be5c84c 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -53,7 +53,6 @@
private static final Field MESSAGE_QUEUE_MESSAGES_FIELD;
private static final Field MESSAGE_NEXT_FIELD;
private static final Field MESSAGE_WHEN_FIELD;
- private static Field MESSAGE_QUEUE_USE_CONCURRENT_FIELD = null;
private Looper mLooper;
private MessageQueue mQueue;
@@ -64,14 +63,6 @@
static {
try {
- MESSAGE_QUEUE_USE_CONCURRENT_FIELD =
- MessageQueue.class.getDeclaredField("mUseConcurrent");
- MESSAGE_QUEUE_USE_CONCURRENT_FIELD.setAccessible(true);
- } catch (NoSuchFieldException ignored) {
- // Ignore - maybe this is not CombinedMessageQueue?
- }
-
- try {
MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
@@ -155,15 +146,6 @@
mLooper = l;
mQueue = mLooper.getQueue();
mHandler = new Handler(mLooper);
-
- // If we are using CombinedMessageQueue, we need to disable concurrent mode for testing.
- if (MESSAGE_QUEUE_USE_CONCURRENT_FIELD != null) {
- try {
- MESSAGE_QUEUE_USE_CONCURRENT_FIELD.set(mQueue, false);
- } catch (IllegalAccessException e) {
- throw new RuntimeException(e);
- }
- }
}
/**
diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java
index 1bcfaf6..56b0a25 100644
--- a/tests/utils/testutils/java/android/os/test/TestLooper.java
+++ b/tests/utils/testutils/java/android/os/test/TestLooper.java
@@ -100,18 +100,6 @@
throw new RuntimeException("Reflection error constructing or accessing looper", e);
}
- // If we are using CombinedMessageQueue, we need to disable concurrent mode for testing.
- try {
- Field messageQueueUseConcurrentField =
- MessageQueue.class.getDeclaredField("mUseConcurrent");
- messageQueueUseConcurrentField.setAccessible(true);
- messageQueueUseConcurrentField.set(mLooper.getQueue(), false);
- } catch (NoSuchFieldException e) {
- // Ignore - maybe this is not CombinedMessageQueue?
- } catch (IllegalAccessException e) {
- throw new RuntimeException("Reflection error constructing or accessing looper", e);
- }
-
mClock = clock;
}