Merge "Move fontchain_lint to build/core/tasks" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index bd69385..59a1cbc 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -18,6 +18,7 @@
":android.content.pm.flags-aconfig-java{.generated_srcjars}",
":android.content.res.flags-aconfig-java{.generated_srcjars}",
":android.hardware.biometrics.flags-aconfig-java{.generated_srcjars}",
+ ":android.media.playback.flags-aconfig-java{.generated_srcjars}",
":android.net.vcn.flags-aconfig-java{.generated_srcjars}",
":android.nfc.flags-aconfig-java{.generated_srcjars}",
":android.os.flags-aconfig-java{.generated_srcjars}",
@@ -30,6 +31,25 @@
// !!! KEEP THIS LIST ALPHABETICAL !!!
]
+stubs_defaults {
+ name: "framework-minus-apex-aconfig-declarations",
+ aconfig_declarations: [
+ "android.content.pm.flags-aconfig",
+ "android.content.res.flags-aconfig",
+ "android.hardware.biometrics.flags-aconfig",
+ "android.media.playback.flags-aconfig",
+ "android.net.vcn.flags-aconfig",
+ "android.nfc.flags-aconfig",
+ "android.os.flags-aconfig",
+ "android.security.flags-aconfig",
+ "com.android.hardware.camera2-aconfig",
+ "com.android.hardware.input.input-aconfig",
+ "com.android.window.flags.window-aconfig",
+ "com.android.text.flags-aconfig",
+ "com.android.net.flags-aconfig",
+ ],
+}
+
filegroup {
name: "framework-minus-apex-aconfig-srcjars",
srcs: aconfig_srcjars,
diff --git a/api/Android.bp b/api/Android.bp
index 7dd13e3..7fb427e 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -310,6 +310,7 @@
// classpath (or sources) somehow.
stubs_defaults {
name: "android-non-updatable-stubs-defaults",
+ defaults: ["framework-minus-apex-aconfig-declarations"],
srcs: [":android-non-updatable-stub-sources"],
sdk_version: "none",
system_modules: "none",
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 1994058..43163b3 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -58,3 +58,10 @@
bug: "290312729"
is_fixed_read_only: true
}
+
+flag {
+ name: "report_primary_auth_attempts"
+ namespace: "biometrics"
+ description: "Report primary auth attempts from LockSettingsService"
+ bug: "285053096"
+}
diff --git a/core/java/com/android/internal/widget/ILockSettingsStateListener.aidl b/core/java/com/android/internal/widget/ILockSettingsStateListener.aidl
new file mode 100644
index 0000000..25e3003
--- /dev/null
+++ b/core/java/com/android/internal/widget/ILockSettingsStateListener.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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.internal.widget;
+
+/**
+ * Callback interface between LockSettingService and other system services to be notified about the
+ * state of primary authentication (i.e. PIN/pattern/password).
+ * @hide
+ */
+oneway interface ILockSettingsStateListener {
+ /**
+ * Defines behavior in response to a successful authentication
+ * @param userId The user Id for the requested authentication
+ */
+ void onAuthenticationSucceeded(int userId);
+
+ /**
+ * Defines behavior in response to a failed authentication
+ * @param userId The user Id for the requested authentication
+ */
+ void onAuthenticationFailed(int userId);
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java
index 8114e1f..627e877 100644
--- a/core/java/com/android/internal/widget/LockSettingsInternal.java
+++ b/core/java/com/android/internal/widget/LockSettingsInternal.java
@@ -166,4 +166,16 @@
* Refreshes pending strong auth timeout with the latest admin requirement set by device policy.
*/
public abstract void refreshStrongAuthTimeout(int userId);
+
+ /**
+ * Register a LockSettingsStateListener
+ * @param listener The listener to be registered
+ */
+ public abstract void registerLockSettingsStateListener(ILockSettingsStateListener listener);
+
+ /**
+ * Unregister a LockSettingsStateListener
+ * @param listener The listener to be unregistered
+ */
+ public abstract void unregisterLockSettingsStateListener(ILockSettingsStateListener listener);
}
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 1dd22cf..a733d17 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -218,7 +218,7 @@
* stencil buffer may be needed. Views that use a functor to draw will be forced onto a layer.
*/
void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer) {
- if (mDamageGenerationId == info.damageGenerationId) {
+ if (mDamageGenerationId == info.damageGenerationId && mDamageGenerationId != 0) {
// We hit the same node a second time in the same tree. We don't know the minimal
// damage rect anymore, so just push the biggest we can onto our parent's transform
// We push directly onto parent in case we are clipped to bounds but have moved position.
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 572635a..4d03bf1 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -263,7 +263,7 @@
DisplayList mDisplayList;
DisplayList mStagingDisplayList;
- int64_t mDamageGenerationId;
+ int64_t mDamageGenerationId = 0;
friend class AnimatorManager;
AnimatorManager mAnimatorManager;
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index ad09082..c81ce26 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -16,6 +16,7 @@
package com.android.server.locksettings;
+import static android.security.Flags.reportPrimaryAuthAttempts;
import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE;
import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS;
@@ -90,6 +91,7 @@
import android.os.IBinder;
import android.os.IProgressListener;
import android.os.Process;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
@@ -135,6 +137,7 @@
import com.android.internal.util.Preconditions;
import com.android.internal.widget.ICheckCredentialProgressCallback;
import com.android.internal.widget.ILockSettings;
+import com.android.internal.widget.ILockSettingsStateListener;
import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
import com.android.internal.widget.LockPatternUtils;
@@ -327,6 +330,9 @@
private HashMap<UserHandle, UserManager> mUserManagerCache = new HashMap<>();
+ private final RemoteCallbackList<ILockSettingsStateListener> mLockSettingsStateListeners =
+ new RemoteCallbackList<>();
+
// This class manages life cycle events for encrypted users on File Based Encryption (FBE)
// devices. The most basic of these is to show/hide notifications about missing features until
// the user unlocks the account and credential-encrypted storage is available.
@@ -2342,9 +2348,37 @@
requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId);
}
}
+ if (reportPrimaryAuthAttempts()) {
+ final boolean success =
+ response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK;
+ notifyLockSettingsStateListeners(success, userId);
+ }
return response;
}
+ private void notifyLockSettingsStateListeners(boolean success, int userId) {
+ int i = mLockSettingsStateListeners.beginBroadcast();
+ try {
+ while (i > 0) {
+ i--;
+ try {
+ if (success) {
+ mLockSettingsStateListeners.getBroadcastItem(i)
+ .onAuthenticationSucceeded(userId);
+ } else {
+ mLockSettingsStateListeners.getBroadcastItem(i)
+ .onAuthenticationFailed(userId);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception while notifying LockSettingsStateListener:"
+ + " success = " + success + ", userId = " + userId, e);
+ }
+ }
+ } finally {
+ mLockSettingsStateListeners.finishBroadcast();
+ }
+ }
+
@Override
public VerifyCredentialResponse verifyTiedProfileChallenge(LockscreenCredential credential,
int userId, @LockPatternUtils.VerifyFlag int flags) {
@@ -3662,6 +3696,18 @@
public void refreshStrongAuthTimeout(int userId) {
mStrongAuth.refreshStrongAuthTimeout(userId);
}
+
+ @Override
+ public void registerLockSettingsStateListener(
+ @NonNull ILockSettingsStateListener listener) {
+ mLockSettingsStateListeners.register(listener);
+ }
+
+ @Override
+ public void unregisterLockSettingsStateListener(
+ @NonNull ILockSettingsStateListener listener) {
+ mLockSettingsStateListeners.unregister(listener);
+ }
}
private class RebootEscrowCallbacks implements RebootEscrowManager.Callbacks {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 5a62d92..5081198 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -16,6 +16,8 @@
package com.android.server.locksettings;
+import static android.security.Flags.FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS;
+
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
@@ -30,25 +32,30 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.PropertyInvalidatedCache;
+import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.service.gatekeeper.GateKeeperResponse;
import android.text.TextUtils;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.widget.ILockSettingsStateListener;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.VerifyCredentialResponse;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -59,6 +66,7 @@
@Presubmit
@RunWith(AndroidJUnit4.class)
public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setUp() {
@@ -399,6 +407,60 @@
}
@Test
+ public void testVerifyCredential_notifyLockSettingsStateListeners_whenGoodPassword()
+ throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS);
+ final LockscreenCredential password = newPassword("password");
+ setCredential(PRIMARY_USER_ID, password);
+ final ILockSettingsStateListener listener = mockLockSettingsStateListener();
+ mLocalService.registerLockSettingsStateListener(listener);
+
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */)
+ .getResponseCode());
+
+ verify(listener).onAuthenticationSucceeded(PRIMARY_USER_ID);
+ }
+
+ @Test
+ public void testVerifyCredential_notifyLockSettingsStateListeners_whenBadPassword()
+ throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS);
+ final LockscreenCredential password = newPassword("password");
+ setCredential(PRIMARY_USER_ID, password);
+ final LockscreenCredential badPassword = newPassword("badPassword");
+ final ILockSettingsStateListener listener = mockLockSettingsStateListener();
+ mLocalService.registerLockSettingsStateListener(listener);
+
+ assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
+ mService.verifyCredential(badPassword, PRIMARY_USER_ID, 0 /* flags */)
+ .getResponseCode());
+
+ verify(listener).onAuthenticationFailed(PRIMARY_USER_ID);
+ }
+
+ @Test
+ public void testLockSettingsStateListener_registeredThenUnregistered() throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS);
+ final LockscreenCredential password = newPassword("password");
+ setCredential(PRIMARY_USER_ID, password);
+ final LockscreenCredential badPassword = newPassword("badPassword");
+ final ILockSettingsStateListener listener = mockLockSettingsStateListener();
+
+ mLocalService.registerLockSettingsStateListener(listener);
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */)
+ .getResponseCode());
+ verify(listener).onAuthenticationSucceeded(PRIMARY_USER_ID);
+
+ mLocalService.unregisterLockSettingsStateListener(listener);
+ assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
+ mService.verifyCredential(badPassword, PRIMARY_USER_ID, 0 /* flags */)
+ .getResponseCode());
+ verify(listener, never()).onAuthenticationFailed(PRIMARY_USER_ID);
+ }
+
+ @Test
public void testSetCredentialNotPossibleInSecureFrpModeDuringSuw() {
setUserSetupComplete(false);
setSecureFrpMode(true);
@@ -537,4 +599,12 @@
assertNotEquals(0, mGateKeeperService.getSecureUserId(userId));
}
}
+
+ private ILockSettingsStateListener mockLockSettingsStateListener() {
+ ILockSettingsStateListener listener = mock(ILockSettingsStateListener.Stub.class);
+ IBinder binder = mock(IBinder.class);
+ when(binder.isBinderAlive()).thenReturn(true);
+ when(listener.asBinder()).thenReturn(binder);
+ return listener;
+ }
}
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index cf4dd79..24699bf 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -56,6 +56,7 @@
#include "java/JavaClassGenerator.h"
#include "java/ManifestClassGenerator.h"
#include "java/ProguardRules.h"
+#include "link/FeatureFlagsFilter.h"
#include "link/Linkers.h"
#include "link/ManifestFixer.h"
#include "link/NoDefaultResourceRemover.h"
@@ -1986,6 +1987,19 @@
context_->SetNameManglerPolicy(NameManglerPolicy{context_->GetCompilationPackage()});
context_->SetSplitNameDependencies(app_info_.split_name_dependencies);
+ FeatureFlagsFilterOptions flags_filter_options;
+ if (context_->GetMinSdkVersion() > SDK_UPSIDE_DOWN_CAKE) {
+ // For API version > U, PackageManager will dynamically read the flag values and disable
+ // manifest elements accordingly when parsing the manifest.
+ // For API version <= U, we remove disabled elements from the manifest with the filter.
+ flags_filter_options.remove_disabled_elements = false;
+ flags_filter_options.flags_must_have_value = false;
+ }
+ FeatureFlagsFilter flags_filter(options_.feature_flag_values, flags_filter_options);
+ if (!flags_filter.Consume(context_, manifest_xml.get())) {
+ return 1;
+ }
+
// Override the package ID when it is "android".
if (context_->GetCompilationPackage() == "android") {
context_->SetPackageId(kAndroidPackageId);
@@ -2530,7 +2544,7 @@
}
for (const std::string& arg : all_feature_flags_args) {
- if (ParseFeatureFlagsParameter(arg, context.GetDiagnostics(), &options_.feature_flag_values)) {
+ if (!ParseFeatureFlagsParameter(arg, context.GetDiagnostics(), &options_.feature_flag_values)) {
return 1;
}
}
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index 26713fd..dc18b1c 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -330,7 +330,11 @@
"should only be used together with the --static-lib flag.",
&options_.merge_only);
AddOptionalSwitch("-v", "Enables verbose logging.", &verbose_);
- AddOptionalFlagList("--feature-flags", "Placeholder, to be implemented.", &feature_flags_args_);
+ AddOptionalFlagList("--feature-flags",
+ "Specify the values of feature flags. The pairs in the argument\n"
+ "are separated by ',' and the name is separated from the value by '='.\n"
+ "Example: \"flag1=true,flag2=false,flag3=\" (flag3 has no given value).",
+ &feature_flags_args_);
}
int Action(const std::vector<std::string>& args) override;
diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp
index 725a1b8..7ceb351 100644
--- a/tools/aapt2/cmd/Link_test.cpp
+++ b/tools/aapt2/cmd/Link_test.cpp
@@ -16,11 +16,10 @@
#include "Link.h"
-#include <android-base/file.h>
-
-#include "AppInfo.h"
#include "Diagnostics.h"
#include "LoadedApk.h"
+#include "android-base/file.h"
+#include "android-base/stringprintf.h"
#include "test/Test.h"
using testing::Eq;
@@ -993,4 +992,213 @@
ASSERT_FALSE(Link(link_args, &diag));
}
+static void BuildSDKWithFeatureFlagAttr(const std::string& apk_path, const std::string& java_path,
+ CommandTestFixture* fixture, android::IDiagnostics* diag) {
+ const std::string android_values =
+ R"(<resources>
+ <staging-public-group type="attr" first-id="0x01fe0063">
+ <public name="featureFlag" />
+ </staging-public-group>
+ <attr name="featureFlag" format="string" />
+ </resources>)";
+
+ SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = android_values};
+ BuildSDK({source_xml}, apk_path, java_path, fixture, diag);
+}
+
+TEST_F(LinkTest, FeatureFlagDisabled_SdkAtMostUDC) {
+ StdErrDiagnostics diag;
+ const std::string android_apk = GetTestPath("android.apk");
+ const std::string android_java = GetTestPath("android-java");
+ BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+ const std::string manifest_contents = android::base::StringPrintf(
+ R"(<uses-sdk android:minSdkVersion="%d" />"
+ <permission android:name="FOO" android:featureFlag="flag" />)",
+ SDK_UPSIDE_DOWN_CAKE);
+ auto app_manifest = ManifestBuilder(this)
+ .SetPackageName("com.example.app")
+ .AddContents(manifest_contents)
+ .Build();
+
+ auto app_link_args = LinkCommandBuilder(this)
+ .SetManifestFile(app_manifest)
+ .AddParameter("-I", android_apk)
+ .AddParameter("--feature-flags", "flag=false");
+
+ const std::string app_apk = GetTestPath("app.apk");
+ BuildApk({}, app_apk, std::move(app_link_args), this, &diag);
+
+ // Permission element should be removed if flag is disabled
+ auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag);
+ ASSERT_THAT(apk, NotNull());
+ auto apk_manifest = apk->GetManifest();
+ ASSERT_THAT(apk_manifest, NotNull());
+ auto root = apk_manifest->root.get();
+ ASSERT_THAT(root, NotNull());
+ auto maybe_removed = root->FindChild({}, "permission");
+ ASSERT_THAT(maybe_removed, IsNull());
+}
+
+TEST_F(LinkTest, FeatureFlagEnabled_SdkAtMostUDC) {
+ StdErrDiagnostics diag;
+ const std::string android_apk = GetTestPath("android.apk");
+ const std::string android_java = GetTestPath("android-java");
+ BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+ const std::string manifest_contents = android::base::StringPrintf(
+ R"(<uses-sdk android:minSdkVersion="%d" />"
+ <permission android:name="FOO" android:featureFlag="flag" />)",
+ SDK_UPSIDE_DOWN_CAKE);
+ auto app_manifest = ManifestBuilder(this)
+ .SetPackageName("com.example.app")
+ .AddContents(manifest_contents)
+ .Build();
+
+ auto app_link_args = LinkCommandBuilder(this)
+ .SetManifestFile(app_manifest)
+ .AddParameter("-I", android_apk)
+ .AddParameter("--feature-flags", "flag=true");
+
+ const std::string app_apk = GetTestPath("app.apk");
+ BuildApk({}, app_apk, std::move(app_link_args), this, &diag);
+
+ // Permission element should be kept if flag is enabled
+ auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag);
+ ASSERT_THAT(apk, NotNull());
+ auto apk_manifest = apk->GetManifest();
+ ASSERT_THAT(apk_manifest, NotNull());
+ auto root = apk_manifest->root.get();
+ ASSERT_THAT(root, NotNull());
+ auto maybe_removed = root->FindChild({}, "permission");
+ ASSERT_THAT(maybe_removed, NotNull());
+}
+
+TEST_F(LinkTest, FeatureFlagWithNoValue_SdkAtMostUDC) {
+ StdErrDiagnostics diag;
+ const std::string android_apk = GetTestPath("android.apk");
+ const std::string android_java = GetTestPath("android-java");
+ BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+ const std::string manifest_contents = android::base::StringPrintf(
+ R"(<uses-sdk android:minSdkVersion="%d" />"
+ <permission android:name="FOO" android:featureFlag="flag" />)",
+ SDK_UPSIDE_DOWN_CAKE);
+ auto app_manifest = ManifestBuilder(this)
+ .SetPackageName("com.example.app")
+ .AddContents(manifest_contents)
+ .Build();
+
+ auto app_link_args = LinkCommandBuilder(this)
+ .SetManifestFile(app_manifest)
+ .AddParameter("-I", android_apk)
+ .AddParameter("--feature-flags", "flag=");
+
+ // Flags must have values if <= UDC
+ const std::string app_apk = GetTestPath("app.apk");
+ ASSERT_FALSE(Link(app_link_args.Build(app_apk), &diag));
+}
+
+TEST_F(LinkTest, FeatureFlagDisabled_SdkAfterUDC) {
+ StdErrDiagnostics diag;
+ const std::string android_apk = GetTestPath("android.apk");
+ const std::string android_java = GetTestPath("android-java");
+ BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+ const std::string manifest_contents = android::base::StringPrintf(
+ R"(<uses-sdk android:minSdkVersion="%d" />"
+ <permission android:name="FOO" android:featureFlag="flag" />)",
+ SDK_CUR_DEVELOPMENT);
+ auto app_manifest = ManifestBuilder(this)
+ .SetPackageName("com.example.app")
+ .AddContents(manifest_contents)
+ .Build();
+
+ auto app_link_args = LinkCommandBuilder(this)
+ .SetManifestFile(app_manifest)
+ .AddParameter("-I", android_apk)
+ .AddParameter("--feature-flags", "flag=false");
+
+ const std::string app_apk = GetTestPath("app.apk");
+ BuildApk({}, app_apk, std::move(app_link_args), this, &diag);
+
+ // Permission element should be kept if > UDC, regardless of flag value
+ auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag);
+ ASSERT_THAT(apk, NotNull());
+ auto apk_manifest = apk->GetManifest();
+ ASSERT_THAT(apk_manifest, NotNull());
+ auto root = apk_manifest->root.get();
+ ASSERT_THAT(root, NotNull());
+ auto maybe_removed = root->FindChild({}, "permission");
+ ASSERT_THAT(maybe_removed, NotNull());
+}
+
+TEST_F(LinkTest, FeatureFlagEnabled_SdkAfterUDC) {
+ StdErrDiagnostics diag;
+ const std::string android_apk = GetTestPath("android.apk");
+ const std::string android_java = GetTestPath("android-java");
+ BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+ const std::string manifest_contents = android::base::StringPrintf(
+ R"(<uses-sdk android:minSdkVersion="%d" />"
+ <permission android:name="FOO" android:featureFlag="flag" />)",
+ SDK_CUR_DEVELOPMENT);
+ auto app_manifest = ManifestBuilder(this)
+ .SetPackageName("com.example.app")
+ .AddContents(manifest_contents)
+ .Build();
+
+ auto app_link_args = LinkCommandBuilder(this)
+ .SetManifestFile(app_manifest)
+ .AddParameter("-I", android_apk)
+ .AddParameter("--feature-flags", "flag=true");
+
+ const std::string app_apk = GetTestPath("app.apk");
+ BuildApk({}, app_apk, std::move(app_link_args), this, &diag);
+
+ // Permission element should be kept if > UDC, regardless of flag value
+ auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag);
+ ASSERT_THAT(apk, NotNull());
+ auto apk_manifest = apk->GetManifest();
+ ASSERT_THAT(apk_manifest, NotNull());
+ auto root = apk_manifest->root.get();
+ ASSERT_THAT(root, NotNull());
+ auto maybe_removed = root->FindChild({}, "permission");
+ ASSERT_THAT(maybe_removed, NotNull());
+}
+
+TEST_F(LinkTest, FeatureFlagWithNoValue_SdkAfterUDC) {
+ StdErrDiagnostics diag;
+ const std::string android_apk = GetTestPath("android.apk");
+ const std::string android_java = GetTestPath("android-java");
+ BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+ const std::string manifest_contents = android::base::StringPrintf(
+ R"(<uses-sdk android:minSdkVersion="%d" />"
+ <permission android:name="FOO" android:featureFlag="flag" />)",
+ SDK_CUR_DEVELOPMENT);
+ auto app_manifest = ManifestBuilder(this)
+ .SetPackageName("com.example.app")
+ .AddContents(manifest_contents)
+ .Build();
+
+ auto app_link_args = LinkCommandBuilder(this)
+ .SetManifestFile(app_manifest)
+ .AddParameter("-I", android_apk)
+ .AddParameter("--feature-flags", "flag=");
+
+ const std::string app_apk = GetTestPath("app.apk");
+ BuildApk({}, app_apk, std::move(app_link_args), this, &diag);
+
+ // Permission element should be kept if > UDC, regardless of flag value
+ auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag);
+ ASSERT_THAT(apk, NotNull());
+ auto apk_manifest = apk->GetManifest();
+ ASSERT_THAT(apk_manifest, NotNull());
+ auto root = apk_manifest->root.get();
+ ASSERT_THAT(root, NotNull());
+ auto maybe_removed = root->FindChild({}, "permission");
+ ASSERT_THAT(maybe_removed, NotNull());
+}
+
} // namespace aapt
diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp
index 93c1b61..02e4beae 100644
--- a/tools/aapt2/util/Files.cpp
+++ b/tools/aapt2/util/Files.cpp
@@ -251,10 +251,13 @@
return false;
}
- for (StringPiece line : util::Tokenize(contents, ' ')) {
+ for (StringPiece line : util::Tokenize(contents, '\n')) {
line = util::TrimWhitespace(line);
- if (!line.empty()) {
- out_arglist->emplace_back(line);
+ for (StringPiece arg : util::Tokenize(line, ' ')) {
+ arg = util::TrimWhitespace(arg);
+ if (!arg.empty()) {
+ out_arglist->emplace_back(arg);
+ }
}
}
return true;
@@ -270,10 +273,13 @@
return false;
}
- for (StringPiece line : util::Tokenize(contents, ' ')) {
+ for (StringPiece line : util::Tokenize(contents, '\n')) {
line = util::TrimWhitespace(line);
- if (!line.empty()) {
- out_argset->emplace(line);
+ for (StringPiece arg : util::Tokenize(line, ' ')) {
+ arg = util::TrimWhitespace(arg);
+ if (!arg.empty()) {
+ out_argset->emplace(arg);
+ }
}
}
return true;
diff --git a/tools/aapt2/util/Files_test.cpp b/tools/aapt2/util/Files_test.cpp
index 6c38080..618a3e0 100644
--- a/tools/aapt2/util/Files_test.cpp
+++ b/tools/aapt2/util/Files_test.cpp
@@ -25,6 +25,9 @@
using ::android::base::StringPrintf;
+using ::testing::ElementsAre;
+using ::testing::UnorderedElementsAre;
+
namespace aapt {
namespace file {
@@ -34,9 +37,11 @@
constexpr const char sTestDirSep = '/';
#endif
-class FilesTest : public ::testing::Test {
+class FilesTest : public TestDirectoryFixture {
public:
void SetUp() override {
+ TestDirectoryFixture::SetUp();
+
std::stringstream builder;
builder << "hello" << sDirSep << "there";
expected_path_ = builder.str();
@@ -66,6 +71,42 @@
EXPECT_EQ(expected_path_, base);
}
+TEST_F(FilesTest, AppendArgsFromFile) {
+ const std::string args_file = GetTestPath("args.txt");
+ WriteFile(args_file,
+ " \n"
+ "arg1 arg2 arg3 \n"
+ " arg4 arg5");
+ std::vector<std::string> args;
+ std::string error;
+ ASSERT_TRUE(AppendArgsFromFile(args_file, &args, &error));
+ EXPECT_THAT(args, ElementsAre("arg1", "arg2", "arg3", "arg4", "arg5"));
+}
+
+TEST_F(FilesTest, AppendArgsFromFile_InvalidFile) {
+ std::vector<std::string> args;
+ std::string error;
+ ASSERT_FALSE(AppendArgsFromFile(GetTestPath("not_found.txt"), &args, &error));
+}
+
+TEST_F(FilesTest, AppendSetArgsFromFile) {
+ const std::string args_file = GetTestPath("args.txt");
+ WriteFile(args_file,
+ " \n"
+ "arg2 arg4 arg1 \n"
+ " arg5 arg3");
+ std::unordered_set<std::string> args;
+ std::string error;
+ ASSERT_TRUE(AppendSetArgsFromFile(args_file, &args, &error));
+ EXPECT_THAT(args, UnorderedElementsAre("arg1", "arg2", "arg3", "arg4", "arg5"));
+}
+
+TEST_F(FilesTest, AppendSetArgsFromFile_InvalidFile) {
+ std::unordered_set<std::string> args;
+ std::string error;
+ ASSERT_FALSE(AppendSetArgsFromFile(GetTestPath("not_found.txt"), &args, &error));
+}
+
#ifdef _WIN32
TEST_F(FilesTest, WindowsMkdirsLongPath) {
// Creating directory paths longer than the Windows maximum path length (260 charatcers) should