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