• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.csuite.core.TestUtils.TestUtilsException;
20 import com.android.tradefed.device.ITestDevice;
21 import com.android.tradefed.log.LogUtil.CLog;
22 import com.android.tradefed.util.AaptParser;
23 import com.android.tradefed.util.CommandResult;
24 import com.android.tradefed.util.CommandStatus;
25 import com.android.tradefed.util.IRunUtil;
26 import com.android.tradefed.util.RunUtil;
27 
28 import com.google.common.annotations.VisibleForTesting;
29 
30 import java.io.IOException;
31 import java.nio.file.Path;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.List;
35 import java.util.concurrent.TimeUnit;
36 
37 /** A utility class to install APKs. */
38 public final class ApkInstaller {
39     private static long sCommandTimeOut = TimeUnit.MINUTES.toMillis(4);
40     private final String mDeviceSerial;
41     private final List<Path> mInstalledBaseApks = new ArrayList<>();
42     private final IRunUtil mRunUtil;
43     private final PackageNameParser mPackageNameParser;
44 
getInstance(ITestDevice device)45     public static ApkInstaller getInstance(ITestDevice device) {
46         return getInstance(device.getSerialNumber());
47     }
48 
getInstance(String deviceSerial)49     public static ApkInstaller getInstance(String deviceSerial) {
50         return new ApkInstaller(deviceSerial, new RunUtil(), new AaptPackageNameParser());
51     }
52 
53     @VisibleForTesting
ApkInstaller(String deviceSerial, IRunUtil runUtil, PackageNameParser packageNameParser)54     ApkInstaller(String deviceSerial, IRunUtil runUtil, PackageNameParser packageNameParser) {
55         mDeviceSerial = deviceSerial;
56         mRunUtil = runUtil;
57         mPackageNameParser = packageNameParser;
58     }
59 
60     /**
61      * Installs a package.
62      *
63      * @param apkPath Path to the apk files. Only accept file/directory path containing a single APK
64      *     or split APK files for one package.
65      * @param args Install args for the 'adb install-multiple' command.
66      * @throws ApkInstallerException If the installation failed.
67      * @throws IOException If an IO exception occurred.
68      */
install(Path apkPath, String... args)69     public void install(Path apkPath, String... args) throws ApkInstallerException, IOException {
70         List<Path> apkFilePaths;
71         try {
72             apkFilePaths = TestUtils.listApks(apkPath);
73         } catch (TestUtilsException e) {
74             throw new ApkInstallerException("Failed to list APK files from the path " + apkPath, e);
75         }
76 
77         CLog.d("Installing a package from " + apkPath);
78 
79         String[] cmd = createInstallCommand(apkFilePaths, mDeviceSerial, args);
80 
81         CommandResult res = mRunUtil.runTimedCmd(sCommandTimeOut, cmd);
82         if (res.getStatus() != CommandStatus.SUCCESS) {
83             throw new ApkInstallerException(
84                     String.format(
85                             "Failed to install APKs from the path %s: %s",
86                             apkPath, res.toString()));
87         }
88 
89         mInstalledBaseApks.add(apkFilePaths.get(0));
90 
91         CLog.i("Successfully installed " + apkPath);
92     }
93 
94     /**
95      * Attempts to uninstall all the installed packages.
96      *
97      * <p>When failed to uninstall one of the installed packages, this method will still attempt to
98      * uninstall all other packages before throwing an exception.
99      *
100      * @throws ApkInstallerException when failed to uninstall a package.
101      */
uninstallAllInstalledPackages()102     public void uninstallAllInstalledPackages() throws ApkInstallerException {
103         StringBuilder errorMessage = new StringBuilder();
104         mInstalledBaseApks.forEach(
105                 baseApk -> {
106                     String packageName;
107                     try {
108                         packageName = mPackageNameParser.parsePackageName(baseApk);
109                     } catch (IOException e) {
110                         errorMessage.append(
111                                 String.format(
112                                         "Failed to parse the package name from %s. Reason: %s.\n",
113                                         baseApk, e.getMessage()));
114                         return;
115                     }
116 
117                     String[] cmd =
118                             new String[] {"adb", "-s", mDeviceSerial, "uninstall", packageName};
119 
120                     CommandResult res = mRunUtil.runTimedCmd(sCommandTimeOut, cmd);
121                     if (res.getStatus() != CommandStatus.SUCCESS) {
122                         errorMessage.append(
123                                 String.format(
124                                         "Failed to uninstall package %s from %s. Reason: %s.\n",
125                                         packageName, baseApk, res.toString()));
126                     }
127                 });
128 
129         if (errorMessage.length() > 0) {
130             throw new ApkInstallerException(errorMessage.toString());
131         }
132     }
133 
createInstallCommand( List<Path> apkFilePaths, String deviceSerial, String[] args)134     private String[] createInstallCommand(
135             List<Path> apkFilePaths, String deviceSerial, String[] args) {
136         ArrayList<String> cmd = new ArrayList<>();
137         cmd.addAll(Arrays.asList("adb", "-s", deviceSerial, "install-multiple"));
138 
139         cmd.addAll(Arrays.asList(args));
140 
141         apkFilePaths.stream().map(Path::toString).forEach(cmd::add);
142 
143         return cmd.toArray(new String[cmd.size()]);
144     }
145 
146     /** An exception class representing ApkInstaller error. */
147     public static final class ApkInstallerException extends Exception {
148         /**
149          * Constructs a new {@link ApkInstallerException} with a meaningful error message.
150          *
151          * @param message A error message describing the cause of the error.
152          */
ApkInstallerException(String message)153         private ApkInstallerException(String message) {
154             super(message);
155         }
156 
157         /**
158          * Constructs a new {@link ApkInstallerException} with a meaningful error message, and a
159          * cause.
160          *
161          * @param message A detailed error message.
162          * @param cause A {@link Throwable} capturing the original cause of the {@link
163          *     ApkInstallerException}.
164          */
ApkInstallerException(String message, Throwable cause)165         private ApkInstallerException(String message, Throwable cause) {
166             super(message, cause);
167         }
168 
169         /**
170          * Constructs a new {@link ApkInstallerException} with a cause.
171          *
172          * @param cause A {@link Throwable} capturing the original cause of the {@link
173          *     ApkInstallerException}.
174          */
ApkInstallerException(Throwable cause)175         private ApkInstallerException(Throwable cause) {
176             super(cause);
177         }
178     }
179 
180     private static final class AaptPackageNameParser implements PackageNameParser {
181         @Override
parsePackageName(Path apkFile)182         public String parsePackageName(Path apkFile) throws IOException {
183             String packageName = AaptParser.parse(apkFile.toFile()).getPackageName();
184             if (packageName == null) {
185                 throw new IOException(
186                         String.format("Failed to parse package name with AAPT for %s", apkFile));
187             }
188             return packageName;
189         }
190     }
191 
192     @VisibleForTesting
193     interface PackageNameParser {
parsePackageName(Path apkFile)194         String parsePackageName(Path apkFile) throws IOException;
195     }
196 }
197