1 /* 2 * Copyright (C) 2021 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.csuite.core; 18 19 import com.android.tradefed.config.IConfiguration; 20 import com.android.tradefed.config.Option; 21 import com.android.tradefed.config.Option.Importance; 22 import com.android.tradefed.log.LogUtil.CLog; 23 import com.android.tradefed.util.AaptParser; 24 25 import com.google.common.annotations.VisibleForTesting; 26 27 import java.io.File; 28 import java.io.IOException; 29 import java.io.UncheckedIOException; 30 import java.util.Arrays; 31 import java.util.HashSet; 32 import java.util.Map; 33 import java.util.Set; 34 import java.util.stream.Stream; 35 36 /** Generates modules from package files in a directory. */ 37 public final class DirectoryBasedModuleInfoProvider implements ModuleInfoProvider { 38 @VisibleForTesting static final String DIRECTORY_OPTION = "directory"; 39 private static final String UNKNOWN_PACKAGE = "UNKNOWN_PACKAGE"; 40 41 @VisibleForTesting 42 static final String PACKAGE_INSTALL_FILE_PLACEHOLDER = "{package_install_file}"; 43 44 @VisibleForTesting static final String PACKAGE_PLACEHOLDER = "{package}"; 45 46 // TODO(yuexima): Add split APK directories support. 47 @Option( 48 name = DIRECTORY_OPTION, 49 description = 50 "A directory that contains package installation files for scanning. Modules" 51 + " will be generated using the package installation file names as the" 52 + " module names. Currently, only non-split APK files placed on the root" 53 + " of the directory are scanned. Directories and other type of files will" 54 + " be ignored.", 55 importance = Importance.NEVER) 56 private final Set<File> mDirectories = new HashSet<>(); 57 58 private final PackageNameParser mPackageNameParser; 59 DirectoryBasedModuleInfoProvider()60 public DirectoryBasedModuleInfoProvider() { 61 this(new AaptPackageNameParser()); 62 } 63 64 @VisibleForTesting DirectoryBasedModuleInfoProvider(PackageNameParser packageNameParser)65 DirectoryBasedModuleInfoProvider(PackageNameParser packageNameParser) { 66 mPackageNameParser = packageNameParser; 67 } 68 69 @Override get(IConfiguration configuration)70 public Stream<ModuleInfo> get(IConfiguration configuration) throws IOException { 71 ModuleTemplate template = ModuleTemplate.loadFrom(configuration); 72 return mDirectories.stream() 73 .flatMap(dir -> Arrays.stream(dir.listFiles())) 74 .filter(File::isFile) 75 .filter(file -> file.getPath().toLowerCase().endsWith(".apk")) 76 .map( 77 file -> { 78 try { 79 return new ModuleInfo( 80 file.getName(), 81 template.substitute( 82 file.getName(), 83 Map.of( 84 PACKAGE_PLACEHOLDER, 85 mPackageNameParser.parsePackageName(file), 86 PACKAGE_INSTALL_FILE_PLACEHOLDER, 87 file.getPath()))); 88 } catch (IOException e) { 89 throw new UncheckedIOException(e); 90 } 91 }); 92 } 93 94 private static final class AaptPackageNameParser implements PackageNameParser { 95 @Override 96 public String parsePackageName(File apkFile) throws IOException { 97 AaptParser parseResult = AaptParser.parse(apkFile, AaptParser.AaptVersion.AAPT2); 98 if (parseResult == null || parseResult.getPackageName() == null) { 99 CLog.e(String.format("Failed to parse package name with AAPT for %s", apkFile)); 100 return UNKNOWN_PACKAGE + "_" + apkFile.getName(); 101 } 102 return parseResult.getPackageName(); 103 } 104 } 105 106 @VisibleForTesting 107 interface PackageNameParser { 108 String parsePackageName(File apkFile) throws IOException; 109 } 110 } 111