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.managedprovisioning.task;
18 
19 import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
20 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE;
21 
22 import static com.android.managedprovisioning.task.InstallPackageTask.ERROR_INSTALLATION_FAILED;
23 
24 import static com.google.common.truth.Truth.assertThat;
25 
26 import static org.junit.Assert.fail;
27 
28 import android.app.Instrumentation;
29 import android.content.Context;
30 import android.os.UserHandle;
31 
32 import androidx.test.InstrumentationRegistry;
33 import androidx.test.filters.SmallTest;
34 
35 import com.android.bedstead.permissions.annotations.EnsureHasPermission;
36 import com.android.bedstead.testapp.TestApp;
37 import com.android.bedstead.testapp.TestAppProvider;
38 import com.android.compatibility.common.util.BlockingCallback;
39 import com.android.managedprovisioning.model.ProvisioningParams;
40 
41 import org.junit.Before;
42 import org.junit.Ignore;
43 import org.junit.Test;
44 
45 import java.io.File;
46 import java.io.IOException;
47 import java.io.PrintWriter;
48 
49 
50 @SmallTest
51 public class InstallPackageTaskTest {
52 
53     private final static int RESULT_SUCCESS = -1;
54     private final static int USER_ID = 0;
55     private final static UserHandle USER_HANDLE = new UserHandle(USER_ID);
56     private final static Instrumentation sInstrumentation =
57             androidx.test.platform.app.InstrumentationRegistry.getInstrumentation();
58     private final static Context sContext = InstrumentationRegistry.getTargetContext();
59     private final static String TEST_PACKAGE_NAME = "test.package.name";
60     private static final String INVALID_FILE_CONTENTS = "invalid contents";
61     private static final PackageLocationProvider FILE_NULL_PATH_PROVIDER = () -> null;
62     private static final PackageLocationProvider FILE_INVALID_PATH_PROVIDER =
63             () -> new File("invalid/path");
64     private static final String TEST_APP = "com.android.bedstead.testapp.EmptyTestApp";
65 
66     private InstallPackageBlockingCallback mInstallPackageBlockingCallback;
67 
68     @Before
setUp()69     public void setUp() {
70         mInstallPackageBlockingCallback = new InstallPackageBlockingCallback();
71     }
72 
73     @EnsureHasPermission(WRITE_EXTERNAL_STORAGE)
74     @Test
run_success()75     public void run_success() throws IOException, InterruptedException {
76         File appToInstallFile = getAppToInstallFile();
77         TestApp testApp = writeApkToInstall(appToInstallFile);
78         InstallPackageTask task = new InstallPackageTask(
79                 () -> appToInstallFile,
80                 sContext,
81                 createProvisioningParams(testApp.packageName()),
82                 mInstallPackageBlockingCallback,
83                 testApp.packageName());
84         int resultCode;
85 
86         try {
87             sInstrumentation.runOnMainSync(() -> task.run(USER_ID));
88             resultCode = mInstallPackageBlockingCallback.await();
89         } finally {
90             // TODO(b/191277673): Use TestApp#uninstall(UserHandle) when available to use.
91         }
92 
93         assertThat(resultCode).isEqualTo(RESULT_SUCCESS);
94         assertFileDeleted(appToInstallFile);
95     }
96 
97     @Test
run_fileIsNull_success()98     public void run_fileIsNull_success() throws InterruptedException {
99         InstallPackageTask task = new InstallPackageTask(
100                 FILE_NULL_PATH_PROVIDER,
101                 sContext,
102                 createProvisioningParams(TEST_PACKAGE_NAME),
103                 mInstallPackageBlockingCallback,
104                 TEST_PACKAGE_NAME);
105 
106         task.run(USER_ID);
107         int resultCode = mInstallPackageBlockingCallback.await();
108 
109         assertThat(resultCode).isEqualTo(RESULT_SUCCESS);
110     }
111 
112     @Test
run_fileDoesNotExist_fail()113     public void run_fileDoesNotExist_fail() throws InterruptedException {
114         InstallPackageTask task = new InstallPackageTask(
115                 FILE_INVALID_PATH_PROVIDER,
116                 sContext,
117                 createProvisioningParams(TEST_PACKAGE_NAME),
118                 mInstallPackageBlockingCallback,
119                 TEST_PACKAGE_NAME);
120 
121         sInstrumentation.runOnMainSync(() -> task.run(USER_ID));
122         int resultCode = mInstallPackageBlockingCallback.await();
123 
124         assertThat(resultCode).isEqualTo(ERROR_INSTALLATION_FAILED);
125     }
126 
127     @Ignore("b/191285670")
128     @EnsureHasPermission(WRITE_EXTERNAL_STORAGE)
129     @Test
run_packageAlreadyInstalled_success()130     public void run_packageAlreadyInstalled_success() throws IOException, InterruptedException {
131         File appToInstallFile = getAppToInstallFile();
132         TestApp testApp = writeApkToInstall(appToInstallFile);
133         testApp.install(USER_HANDLE);
134         InstallPackageTask task = new InstallPackageTask(
135                 () -> appToInstallFile,
136                 sContext,
137                 createProvisioningParams(testApp.packageName()),
138                 mInstallPackageBlockingCallback,
139                 testApp.packageName());
140         int resultCode;
141 
142         try {
143             sInstrumentation.runOnMainSync(() -> task.run(USER_ID));
144             resultCode = mInstallPackageBlockingCallback.await();
145         } finally {
146             // TODO(b/191277673): Use TestApp#uninstall(UserHandle) when available to use.
147         }
148 
149         assertThat(resultCode).isEqualTo(RESULT_SUCCESS);
150         assertFileDeleted(appToInstallFile);
151     }
152 
153     @EnsureHasPermission(WRITE_EXTERNAL_STORAGE)
154     @Test
run_invalidApkFile_error()155     public void run_invalidApkFile_error() throws IOException, InterruptedException {
156         File appToInstallFile = getAppToInstallFile();
157         writeInvalidFile(appToInstallFile);
158         InstallPackageTask task = new InstallPackageTask(
159                 () -> appToInstallFile,
160                 sContext,
161                 createProvisioningParams(TEST_PACKAGE_NAME),
162                 mInstallPackageBlockingCallback,
163                 TEST_PACKAGE_NAME);
164         int resultCode;
165 
166         sInstrumentation.runOnMainSync(() -> task.run(USER_ID));
167         resultCode = mInstallPackageBlockingCallback.await();
168 
169         assertThat(resultCode).isEqualTo(ERROR_INSTALLATION_FAILED);
170         assertFileDeleted(appToInstallFile);
171     }
172 
assertFileDeleted(File appToInstallFile)173     private void assertFileDeleted(File appToInstallFile) {
174         if (appToInstallFile.exists()) {
175             appToInstallFile.delete();
176             fail("File " + appToInstallFile.getAbsolutePath() + " was not deleted.");
177         }
178     }
179 
writeInvalidFile(File appToInstallFile)180     private void writeInvalidFile(File appToInstallFile) throws IOException {
181         try (PrintWriter writer = new PrintWriter(appToInstallFile)) {
182             writer.println(INVALID_FILE_CONTENTS);
183         }
184     }
185 
createProvisioningParams(String packageName)186     private static ProvisioningParams createProvisioningParams(String packageName) {
187         return new ProvisioningParams.Builder()
188                 .setProvisioningAction(ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE)
189                 .setDeviceAdminPackageName(packageName)
190                 .build();
191     }
192 
writeApkToInstall(File appToInstallFile)193     private static TestApp writeApkToInstall(File appToInstallFile) throws IOException {
194         TestAppProvider testAppProvider = new TestAppProvider();
195         TestApp testApp = testAppProvider
196                 .query()
197                 // TODO(b/192330233): We use this specific app as it does not have the testOnly flag
198                 .wherePackageName().isEqualTo(TEST_APP)
199                 .get();
200         testApp.writeApkFile(appToInstallFile);
201         return testApp;
202     }
203 
getAppToInstallFile()204     private static File getAppToInstallFile() throws IOException {
205         return File.createTempFile(
206                 /* prefix= */ "test_app" + Math.random() * Integer.MAX_VALUE,
207                 ".apk",
208                 sContext.getCacheDir());
209     }
210 
211     private static class InstallPackageBlockingCallback extends BlockingCallback<Integer>
212             implements AbstractProvisioningTask.Callback {
213         @Override
onSuccess(AbstractProvisioningTask task)214         public void onSuccess(AbstractProvisioningTask task) {
215             callbackTriggered(RESULT_SUCCESS);
216         }
217 
218         @Override
onError( AbstractProvisioningTask task, int errorCode, String errorMessage)219         public void onError(
220                 AbstractProvisioningTask task, int errorCode, String errorMessage) {
221             callbackTriggered(errorCode);
222         }
223     }
224 }
225