diff options
author | 2024-11-07 15:59:21 +0000 | |
---|---|---|
committer | 2024-11-26 22:36:40 +0000 | |
commit | a3d9ad8d83692399056838c5adda3142a9adb491 (patch) | |
tree | 44235569ddde90cf2b70a1c648761ccd9a55457d | |
parent | 86d746b7357fda4838479ef82ae0193aed0f8750 (diff) |
Add APIs for ART managed install files.
Bug: 377887025
Test: atest ArtServiceTests
Test: atest art_standalone_artd_tests
Change-Id: I0836a0523dd80b24b0a0452efec023282237824f
9 files changed, 219 insertions, 4 deletions
diff --git a/artd/artd_test.cc b/artd/artd_test.cc index 27dfbb34c5..0a06a0917d 100644 --- a/artd/artd_test.cc +++ b/artd/artd_test.cc @@ -597,6 +597,7 @@ class ArtdTest : public CommonArtTest { TEST_F(ArtdTest, ConstantsAreInSync) { EXPECT_STREQ(ArtConstants::REASON_VDEX, kReasonVdex); EXPECT_STREQ(ArtConstants::DEX_METADATA_FILE_EXT, kDmExtension); + EXPECT_STREQ(ArtConstants::SECURE_DEX_METADATA_FILE_EXT, kSdmExtension); EXPECT_STREQ(ArtConstants::DEX_METADATA_PROFILE_ENTRY, ProfileCompilationInfo::kDexMetadataProfileEntry); EXPECT_STREQ(ArtConstants::DEX_METADATA_VDEX_ENTRY, VdexFile::kVdexNameInDmFile); diff --git a/artd/binder/com/android/server/art/ArtConstants.aidl b/artd/binder/com/android/server/art/ArtConstants.aidl index 6e438f7efc..53fa75dead 100644 --- a/artd/binder/com/android/server/art/ArtConstants.aidl +++ b/artd/binder/com/android/server/art/ArtConstants.aidl @@ -49,6 +49,13 @@ parcelable ArtConstants { const @utf8InCpp String PROFILE_FILE_EXT = ".prof"; /** + * The file extension of the secure dex metadata file. + * + * Keep in sync with {@code kSdmExtension} in art/libartbase/base/file_utils.h. + */ + const @utf8InCpp String SECURE_DEX_METADATA_FILE_EXT = ".sdm"; + + /** * The name of the profile entry in the dex metadata file. * * Keep in sync with {@code ProfileCompilationInfo::kDexMetadataProfileEntry} in diff --git a/libartbase/base/file_utils.h b/libartbase/base/file_utils.h index 8a25fe1d9f..7f52d03497 100644 --- a/libartbase/base/file_utils.h +++ b/libartbase/base/file_utils.h @@ -38,6 +38,7 @@ static constexpr const char* kOdexExtension = ".odex"; static constexpr const char* kVdexExtension = ".vdex"; static constexpr const char* kArtExtension = ".art"; static constexpr const char* kDmExtension = ".dm"; +static constexpr const char* kSdmExtension = ".sdm"; // These methods return the Android Root, which is the historical location of // the Android "system" directory, containing the built Android artifacts. On diff --git a/libartservice/service/api/system-server-current.txt b/libartservice/service/api/system-server-current.txt index b563e7df5c..b7a3739370 100644 --- a/libartservice/service/api/system-server-current.txt +++ b/libartservice/service/api/system-server-current.txt @@ -1,6 +1,12 @@ // Signature format: 2.0 package com.android.server.art { + @FlaggedApi("com.android.art.flags.art_service_v3") public final class ArtManagedInstallFileHelper { + method @FlaggedApi("com.android.art.flags.art_service_v3") @NonNull public static java.util.List<java.lang.String> filterPathsForApk(@NonNull java.util.List<java.lang.String>, @NonNull String); + method @FlaggedApi("com.android.art.flags.art_service_v3") @NonNull public static String getTargetPathForApk(@NonNull String, @NonNull String); + method @FlaggedApi("com.android.art.flags.art_service_v3") public static boolean isArtManaged(@NonNull String); + } + public final class ArtManagerLocal { ctor @Deprecated public ArtManagerLocal(); ctor public ArtManagerLocal(@NonNull android.content.Context); diff --git a/libartservice/service/java/com/android/server/art/ArtManagedInstallFileHelper.java b/libartservice/service/java/com/android/server/art/ArtManagedInstallFileHelper.java new file mode 100644 index 0000000000..dd040165e3 --- /dev/null +++ b/libartservice/service/java/com/android/server/art/ArtManagedInstallFileHelper.java @@ -0,0 +1,94 @@ +/* + * 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.art; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Build; + +import androidx.annotation.RequiresApi; + +import com.android.art.flags.Flags; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Helper class for <i>ART-managed install files</i> (files installed by Package Manager + * and managed by ART). + * + * @hide + */ +@FlaggedApi(Flags.FLAG_ART_SERVICE_V3) +@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) +@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +public final class ArtManagedInstallFileHelper { + private static final List<String> FILE_TYPES = List.of(ArtConstants.DEX_METADATA_FILE_EXT, + ArtConstants.PROFILE_FILE_EXT, ArtConstants.SECURE_DEX_METADATA_FILE_EXT); + + private ArtManagedInstallFileHelper() {} + + /** + * Returns whether the file at the given path is an <i>ART-managed install file</i>. This + * is a pure string operation on the input and does not involve any I/O. + */ + @FlaggedApi(Flags.FLAG_ART_SERVICE_V3) + public static boolean isArtManaged(@NonNull String path) { + return FILE_TYPES.stream().anyMatch(ext -> path.endsWith(ext)); + } + + /** + * Returns the subset of the given paths that are paths to the <i>ART-managed install files</i> + * corresponding to the given APK path. This is a pure string operation on the inputs and does + * not involve any I/O. + * + * Note that the files in different directories than the APK are not considered corresponding to + * the APK. + */ + @FlaggedApi(Flags.FLAG_ART_SERVICE_V3) + public static @NonNull List<String> filterPathsForApk( + @NonNull List<String> paths, @NonNull String apkPath) { + Set<String> candidates = FILE_TYPES.stream() + .map(ext -> Utils.replaceFileExtension(apkPath, ext)) + .collect(Collectors.toSet()); + return paths.stream().filter(path -> candidates.contains(path)).toList(); + } + + /** + * Rewrites the path to the <i>ART-managed install file</i> so that it corresponds to the given + * APK path. This is a pure string operation on the inputs and does not involve any I/O. + * + * Note that the result path is always in the same directory as the APK, in order to correspond + * to the APK. + * + * @throws IllegalArgumentException if {@code originalPath} does not represent an <i>ART-managed + * install file</i> + */ + @FlaggedApi(Flags.FLAG_ART_SERVICE_V3) + public static @NonNull String getTargetPathForApk( + @NonNull String originalPath, @NonNull String apkPath) { + for (String ext : FILE_TYPES) { + if (originalPath.endsWith(ext)) { + return Utils.replaceFileExtension(apkPath, ext); + } + } + throw new IllegalArgumentException( + "Illegal ART managed install file path '" + originalPath + "'"); + } +} diff --git a/libartservice/service/java/com/android/server/art/DexMetadataHelper.java b/libartservice/service/java/com/android/server/art/DexMetadataHelper.java index f5b2a81251..9742c5f40e 100644 --- a/libartservice/service/java/com/android/server/art/DexMetadataHelper.java +++ b/libartservice/service/java/com/android/server/art/DexMetadataHelper.java @@ -86,10 +86,7 @@ public class DexMetadataHelper { @NonNull public static String getDmPath(@NonNull DexMetadataPath dmPath) { - String dexPath = dmPath.dexPath; - int pos = dexPath.lastIndexOf("."); - return (pos != -1 ? dexPath.substring(0, pos) : dexPath) - + ArtConstants.DEX_METADATA_FILE_EXT; + return Utils.replaceFileExtension(dmPath.dexPath, ArtConstants.DEX_METADATA_FILE_EXT); } private static @DexMetadata.Type int getType(@NonNull ZipFile zipFile) { diff --git a/libartservice/service/java/com/android/server/art/Utils.java b/libartservice/service/java/com/android/server/art/Utils.java index 600671fcb2..e042c3e106 100644 --- a/libartservice/service/java/com/android/server/art/Utils.java +++ b/libartservice/service/java/com/android/server/art/Utils.java @@ -501,6 +501,19 @@ public final class Utils { return path.length() == prefixLen || path.charAt(prefixLen) == '/'; } + /** + * Replaces the file extension of the given path with the given new extension. + * + * @param path the path to replace the extension for + * @param newExtension the new extension, including the leading dot + */ + @NonNull + public static String replaceFileExtension(@NonNull String path, @NonNull String newExtension) { + int pos = path.lastIndexOf('.'); + int slashPos = path.indexOf('/', pos); + return ((pos != -1 && slashPos == -1) ? path.substring(0, pos) : path) + newExtension; + } + @AutoValue public abstract static class Abi { static @NonNull Abi create( diff --git a/libartservice/service/javatests/com/android/server/art/ArtManagedInstallFileHelperTest.java b/libartservice/service/javatests/com/android/server/art/ArtManagedInstallFileHelperTest.java new file mode 100644 index 0000000000..4b13716eaa --- /dev/null +++ b/libartservice/service/javatests/com/android/server/art/ArtManagedInstallFileHelperTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.art; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ArtManagedInstallFileHelperTest { + @Test + public void testIsArtManaged() throws Exception { + assertThat(ArtManagedInstallFileHelper.isArtManaged("/foo/bar.dm")).isTrue(); + assertThat(ArtManagedInstallFileHelper.isArtManaged("/foo/bar.prof")).isTrue(); + assertThat(ArtManagedInstallFileHelper.isArtManaged("/foo/bar.sdm")).isTrue(); + assertThat(ArtManagedInstallFileHelper.isArtManaged("/foo/bar.abc")).isFalse(); + } + + @Test + public void testFilterPathsForApk() throws Exception { + assertThat(ArtManagedInstallFileHelper.filterPathsForApk( + List.of("/foo/bar.dm", "/foo/bar.prof", "/foo/bar.sdm", "/foo/bar.abc", + "/foo/baz.dm"), + "/foo/bar.apk")) + .containsExactly("/foo/bar.dm", "/foo/bar.prof", "/foo/bar.sdm"); + + // Filenames don't match. + assertThat(ArtManagedInstallFileHelper.filterPathsForApk( + List.of("/foo/bar.dm", "/foo/bar.prof", "/foo/bar.sdm", "/foo/bar.abc", + "/foo/baz.dm"), + "/foo/qux.apk")) + .isEmpty(); + + // Directories don't match. + assertThat(ArtManagedInstallFileHelper.filterPathsForApk( + List.of("/foo/bar.dm", "/foo/bar.prof", "/foo/bar.sdm", "/foo/bar.abc", + "/foo/baz.dm"), + "/quz/bar.apk")) + .isEmpty(); + } + + @Test + public void testGetTargetPathForApk() throws Exception { + assertThat(ArtManagedInstallFileHelper.getTargetPathForApk( + "/foo/bar.dm", "/somewhere/base.apk")) + .isEqualTo("/somewhere/base.dm"); + assertThat(ArtManagedInstallFileHelper.getTargetPathForApk( + "/foo/bar.prof", "/somewhere/base.apk")) + .isEqualTo("/somewhere/base.prof"); + assertThat(ArtManagedInstallFileHelper.getTargetPathForApk( + "/foo/bar.sdm", "/somewhere/base.apk")) + .isEqualTo("/somewhere/base.sdm"); + + assertThrows(IllegalArgumentException.class, () -> { + ArtManagedInstallFileHelper.getTargetPathForApk("/foo/bar.abc", "/somewhere/base.apk"); + }); + } +} diff --git a/libartservice/service/javatests/com/android/server/art/UtilsTest.java b/libartservice/service/javatests/com/android/server/art/UtilsTest.java index b3220ffdff..5dd484e021 100644 --- a/libartservice/service/javatests/com/android/server/art/UtilsTest.java +++ b/libartservice/service/javatests/com/android/server/art/UtilsTest.java @@ -220,4 +220,19 @@ public class UtilsTest { assertThat(Utils.pathStartsWith("/", "/")).isTrue(); assertThat(Utils.pathStartsWith("/", "/a")).isFalse(); } + + @Test + public void testReplaceFileExtension() { + assertThat(Utils.replaceFileExtension("/directory/file.apk", ".dm")) + .isEqualTo("/directory/file.dm"); + assertThat(Utils.replaceFileExtension("/directory/file", ".dm")) + .isEqualTo("/directory/file.dm"); + assertThat(Utils.replaceFileExtension("/.directory/file.apk", ".dm")) + .isEqualTo("/.directory/file.dm"); + assertThat(Utils.replaceFileExtension("/.directory/file", ".dm")) + .isEqualTo("/.directory/file.dm"); + assertThat(Utils.replaceFileExtension("/directory/file.apk", "")) + .isEqualTo("/directory/file"); + assertThat(Utils.replaceFileExtension("", ".dm")).isEqualTo(".dm"); + } } |