1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.art; 18 19 import android.annotation.FlaggedApi; 20 import android.annotation.NonNull; 21 import android.annotation.SystemApi; 22 import android.os.Build; 23 24 import androidx.annotation.RequiresApi; 25 26 import com.android.art.flags.Flags; 27 28 import java.nio.file.Path; 29 import java.nio.file.Paths; 30 import java.util.List; 31 import java.util.Set; 32 import java.util.stream.Collectors; 33 import java.util.stream.Stream; 34 35 /** 36 * Helper class for <i>ART-managed install files</i> (files installed by Package Manager 37 * and managed by ART). 38 * 39 * @hide 40 */ 41 @FlaggedApi(Flags.FLAG_ART_SERVICE_V3) 42 @SystemApi(client = SystemApi.Client.SYSTEM_SERVER) 43 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 44 public final class ArtManagedInstallFileHelper { 45 private static final List<String> FILE_TYPES = List.of(ArtConstants.DEX_METADATA_FILE_EXT, 46 ArtConstants.PROFILE_FILE_EXT, ArtConstants.SECURE_DEX_METADATA_FILE_EXT); 47 private static final List<String> SDM_SUFFIXES = 48 Utils.getNativeIsas() 49 .stream() 50 .map(isa -> "." + isa + ArtConstants.SECURE_DEX_METADATA_FILE_EXT) 51 .toList(); 52 ArtManagedInstallFileHelper()53 private ArtManagedInstallFileHelper() {} 54 55 /** 56 * Returns whether the file at the given path is an <i>ART-managed install file</i>. This 57 * is a pure string operation on the input and does not involve any I/O. 58 */ 59 @FlaggedApi(Flags.FLAG_ART_SERVICE_V3) isArtManaged(@onNull String path)60 public static boolean isArtManaged(@NonNull String path) { 61 return FILE_TYPES.stream().anyMatch(ext -> path.endsWith(ext)); 62 } 63 64 /** 65 * Returns the subset of the given paths that are paths to the <i>ART-managed install files</i> 66 * corresponding to the given APK path. This is a pure string operation on the inputs and does 67 * not involve any I/O. 68 * 69 * Note that the files in different directories than the APK are not considered corresponding to 70 * the APK. 71 */ 72 @FlaggedApi(Flags.FLAG_ART_SERVICE_V3) filterPathsForApk( @onNull List<String> paths, @NonNull String apkPath)73 public static @NonNull List<String> filterPathsForApk( 74 @NonNull List<String> paths, @NonNull String apkPath) { 75 Set<String> candidates = 76 FILE_TYPES.stream() 77 .flatMap(ext 78 -> ext.equals(ArtConstants.SECURE_DEX_METADATA_FILE_EXT) 79 ? SDM_SUFFIXES.stream().map(suffix 80 -> Utils.replaceFileExtension(apkPath, suffix)) 81 : Stream.of(Utils.replaceFileExtension(apkPath, ext))) 82 .collect(Collectors.toSet()); 83 return paths.stream().filter(path -> candidates.contains(path)).toList(); 84 } 85 86 /** 87 * Rewrites the path to the <i>ART-managed install file</i> so that it corresponds to the given 88 * APK path. This is a pure string operation on the inputs and does not involve any I/O. 89 * 90 * Note that the result path is always in the same directory as the APK, in order to correspond 91 * to the APK. 92 * 93 * @throws IllegalArgumentException if {@code originalPath} does not represent an <i>ART-managed 94 * install file</i> 95 */ 96 @FlaggedApi(Flags.FLAG_ART_SERVICE_V3) getTargetPathForApk( @onNull String originalPath, @NonNull String apkPath)97 public static @NonNull String getTargetPathForApk( 98 @NonNull String originalPath, @NonNull String apkPath) { 99 for (String ext : FILE_TYPES) { 100 if (!ext.equals(ArtConstants.SECURE_DEX_METADATA_FILE_EXT) 101 && originalPath.endsWith(ext)) { 102 return Utils.replaceFileExtension(apkPath, ext); 103 } 104 } 105 if (originalPath.endsWith(ArtConstants.SECURE_DEX_METADATA_FILE_EXT)) { 106 for (String suffix : SDM_SUFFIXES) { 107 if (originalPath.endsWith(suffix)) { 108 return Utils.replaceFileExtension(apkPath, suffix); 109 } 110 } 111 AsLog.w("SDM filename '" + originalPath 112 + "' does not contain a valid instruction set name"); 113 Path dirname = Paths.get(apkPath).getParent(); 114 Path basename = Paths.get(originalPath).getFileName(); 115 return (dirname != null ? dirname.resolve(basename) : basename).toString(); 116 } 117 throw new IllegalArgumentException( 118 "Illegal ART managed install file path '" + originalPath + "'"); 119 } 120 } 121