diff options
| -rw-r--r-- | AconfigFlags.bp | 20 | ||||
| -rw-r--r-- | api/Android.bp | 1 | ||||
| -rw-r--r-- | core/java/android/security/flags.aconfig | 7 | ||||
| -rw-r--r-- | core/java/com/android/internal/widget/ILockSettingsStateListener.aidl | 36 | ||||
| -rw-r--r-- | core/java/com/android/internal/widget/LockSettingsInternal.java | 12 | ||||
| -rw-r--r-- | libs/hwui/RenderNode.cpp | 2 | ||||
| -rw-r--r-- | libs/hwui/RenderNode.h | 2 | ||||
| -rw-r--r-- | services/core/java/com/android/server/locksettings/LockSettingsService.java | 46 | ||||
| -rw-r--r-- | services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java | 70 | ||||
| -rw-r--r-- | tools/aapt2/cmd/Link.cpp | 16 | ||||
| -rw-r--r-- | tools/aapt2/cmd/Link.h | 6 | ||||
| -rw-r--r-- | tools/aapt2/cmd/Link_test.cpp | 214 | ||||
| -rw-r--r-- | tools/aapt2/util/Files.cpp | 18 | ||||
| -rw-r--r-- | tools/aapt2/util/Files_test.cpp | 43 |
14 files changed, 479 insertions, 14 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index bd693850f45e..59a1cbc38216 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -18,6 +18,7 @@ aconfig_srcjars = [ ":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 @@ aconfig_srcjars = [ // !!! 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 7dd13e3f8a09..7fb427eb715a 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -310,6 +310,7 @@ packages_to_document = [ // 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 1994058441d5..43163b3b9051 100644 --- a/core/java/android/security/flags.aconfig +++ b/core/java/android/security/flags.aconfig @@ -58,3 +58,10 @@ flag { 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 000000000000..25e30034fe8f --- /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 8114e1fd3bb0..627e8779f9d0 100644 --- a/core/java/com/android/internal/widget/LockSettingsInternal.java +++ b/core/java/com/android/internal/widget/LockSettingsInternal.java @@ -166,4 +166,16 @@ public abstract class LockSettingsInternal { * 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 1dd22cf43c5c..a733d1772757 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -218,7 +218,7 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { * 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 572635a9bd45..4d03bf189a84 100644 --- a/libs/hwui/RenderNode.h +++ b/libs/hwui/RenderNode.h @@ -263,7 +263,7 @@ private: 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 ad090829a2f6..c81ce26b6403 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.Handler; 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.IndentingPrintWriter; 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 @@ public class LockSettingsService extends ILockSettings.Stub { 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 @@ public class LockSettingsService extends ILockSettings.Stub { 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 class LockSettingsService extends ILockSettings.Stub { 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 5a62d92e8e12..5081198f0058 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.junit.Assert.fail; 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 @@ import org.junit.runner.RunWith; @Presubmit @RunWith(AndroidJUnit4.class) public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Before public void setUp() { @@ -399,6 +407,60 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { } @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 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { 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 cf4dd79e3d96..24699bf9a7ff 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 @@ class Linker { 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 @@ int LinkCommand::Action(const std::vector<std::string>& args) { } 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 26713fd92264..dc18b1ccda60 100644 --- a/tools/aapt2/cmd/Link.h +++ b/tools/aapt2/cmd/Link.h @@ -330,7 +330,11 @@ class LinkCommand : public Command { "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 725a1b86f616..7ceb351aaa6f 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 @@ TEST_F(LinkTest, LocaleConfigWrongLocaleFormat) { 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 93c1b61f9a57..02e4beaed949 100644 --- a/tools/aapt2/util/Files.cpp +++ b/tools/aapt2/util/Files.cpp @@ -251,10 +251,13 @@ bool AppendArgsFromFile(StringPiece path, std::vector<std::string>* out_arglist, 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 @@ bool AppendSetArgsFromFile(StringPiece path, std::unordered_set<std::string>* ou 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 6c380808c0df..618a3e0d86ae 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 = '\\'; 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 @@ TEST_F(FilesTest, AppendPathWithLeadingOrTrailingSeparators) { 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 |