1 /* 2 * Copyright (C) 2015 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.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE; 20 import static android.content.pm.PackageManager.INSTALL_ALLOW_TEST; 21 import static android.content.pm.PackageManager.INSTALL_REPLACE_EXISTING; 22 23 import static com.android.managedprovisioning.task.InstallPackageTask.ERROR_INSTALLATION_FAILED; 24 import static com.android.managedprovisioning.task.InstallPackageTask.ERROR_PACKAGE_INVALID; 25 26 import static org.mockito.ArgumentMatchers.anyLong; 27 import static org.mockito.Matchers.any; 28 import static org.mockito.Matchers.anyInt; 29 import static org.mockito.Matchers.anyString; 30 import static org.mockito.Matchers.eq; 31 import static org.mockito.Mockito.never; 32 import static org.mockito.Mockito.timeout; 33 import static org.mockito.Mockito.verify; 34 import static org.mockito.Mockito.verifyNoMoreInteractions; 35 import static org.mockito.Mockito.when; 36 37 import android.app.admin.DevicePolicyManager; 38 import android.content.BroadcastReceiver; 39 import android.content.Context; 40 import android.content.Intent; 41 import android.content.IntentFilter; 42 import android.content.IntentSender; 43 import android.content.pm.PackageInstaller; 44 import android.content.pm.PackageManager; 45 import android.os.Process; 46 import android.os.UserHandle; 47 import android.test.AndroidTestCase; 48 import android.test.suitebuilder.annotation.SmallTest; 49 50 import com.android.managedprovisioning.model.ProvisioningParams; 51 52 import org.mockito.ArgumentCaptor; 53 import org.mockito.Mock; 54 import org.mockito.MockitoAnnotations; 55 import org.mockito.stubbing.Answer; 56 57 import java.io.File; 58 import java.io.FileOutputStream; 59 import java.io.IOException; 60 import java.io.OutputStream; 61 import java.util.Arrays; 62 63 public class InstallPackageTaskTest extends AndroidTestCase { 64 private static final String TEST_PACKAGE_NAME = "com.android.test"; 65 private static final String OTHER_PACKAGE_NAME = "com.android.other"; 66 private static final ProvisioningParams TEST_PARAMS = new ProvisioningParams.Builder() 67 .setDeviceAdminPackageName(TEST_PACKAGE_NAME) 68 .setProvisioningAction(ACTION_PROVISION_MANAGED_DEVICE) 69 .build(); 70 private static final int TEST_USER_ID = 123; 71 private static final byte[] APK_CONTENT = new byte[]{'t', 'e', 's', 't'}; 72 private static final long TIMEOUT = 10000; 73 74 private static int sSessionId = 0; 75 76 @Mock private Context mMockContext; 77 @Mock private PackageManager mPackageManager; 78 @Mock private PackageInstaller mPackageInstaller; 79 @Mock private PackageInstaller.Session mSession; 80 @Mock private OutputStream mSessionWriteStream; 81 @Mock private DevicePolicyManager mDpm; 82 @Mock private AbstractProvisioningTask.Callback mCallback; 83 @Mock private DownloadPackageTask mDownloadPackageTask; 84 private InstallPackageTask mTask; 85 private String mTestPackageLocation; 86 87 @Override setUp()88 protected void setUp() throws Exception { 89 super.setUp(); 90 // this is necessary for mockito to work 91 System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString()); 92 MockitoAnnotations.initMocks(this); 93 94 when(mMockContext.getPackageManager()).thenReturn(mPackageManager); 95 when(mMockContext.getPackageName()).thenReturn(getContext().getPackageName()); 96 when(mPackageManager.getPackageInstaller()).thenReturn(mPackageInstaller); 97 when(mPackageInstaller.createSession(any(PackageInstaller.SessionParams.class))).thenAnswer( 98 (Answer<Integer>) invocation -> sSessionId++); 99 when(mPackageInstaller.openSession(anyInt())).thenReturn(mSession); 100 when(mSession.openWrite(anyString(), anyLong(), anyLong())).thenReturn(mSessionWriteStream); 101 when(mMockContext.registerReceiver(any(BroadcastReceiver.class), 102 any(IntentFilter.class))).thenAnswer( 103 (Answer<Intent>) invocation -> (Intent) getContext().registerReceiver( 104 invocation.getArgument(0), invocation.getArgument(1))); 105 when(mMockContext.getSystemServiceName(eq(DevicePolicyManager.class))) 106 .thenReturn(Context.DEVICE_POLICY_SERVICE); 107 when(mMockContext.getSystemService(eq(Context.DEVICE_POLICY_SERVICE))).thenReturn(mDpm); 108 when(mMockContext.getUser()).thenReturn(Process.myUserHandle()); 109 when(mMockContext.getUserId()).thenReturn(UserHandle.myUserId()); 110 111 mTestPackageLocation = File.createTempFile("test", "apk").getPath(); 112 try (FileOutputStream out = new FileOutputStream(mTestPackageLocation)) { 113 out.write(APK_CONTENT); 114 } 115 116 mTask = new InstallPackageTask(mDownloadPackageTask, mMockContext, TEST_PARAMS, mCallback); 117 } 118 119 @SmallTest testNoDownloadLocation()120 public void testNoDownloadLocation() { 121 // GIVEN no package was downloaded 122 when(mDownloadPackageTask.getDownloadedPackageLocation()).thenReturn(null); 123 124 // WHEN running the InstallPackageTask without specifying an install location 125 mTask.run(TEST_USER_ID); 126 // THEN no package is installed, but we get a success callback 127 verify(mPackageManager, never()).getPackageInstaller(); 128 verify(mCallback).onSuccess(mTask); 129 verifyNoMoreInteractions(mCallback); 130 } 131 132 @SmallTest testSuccess()133 public void testSuccess() throws Exception { 134 // GIVEN a package was downloaded to TEST_LOCATION 135 when(mDownloadPackageTask.getDownloadedPackageLocation()).thenReturn(mTestPackageLocation); 136 137 // WHEN running the InstallPackageTask specifying an install location 138 mTask.run(TEST_USER_ID); 139 140 // THEN package installed is invoked with an install observer 141 IntentSender observer = verifyPackageInstalled(INSTALL_REPLACE_EXISTING); 142 143 // WHEN the package installed callback is invoked with success 144 Intent fillIn = new Intent(); 145 fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, TEST_PACKAGE_NAME); 146 fillIn.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_SUCCESS); 147 observer.sendIntent(getContext(), 0, fillIn, null, null); 148 149 // THEN we receive a success callback 150 verify(mCallback, timeout(TIMEOUT)).onSuccess(mTask); 151 verifyNoMoreInteractions(mCallback); 152 } 153 154 @SmallTest testSuccess_allowTestOnly()155 public void testSuccess_allowTestOnly() throws Exception { 156 // GIVEN a package was downloaded to TEST_LOCATION 157 when(mDownloadPackageTask.getDownloadedPackageLocation()).thenReturn(mTestPackageLocation); 158 // WHEN package to be installed is the current device owner. 159 when(mDpm.isDeviceOwnerApp(eq(TEST_PACKAGE_NAME))).thenReturn(true); 160 161 // WHEN running the InstallPackageTask specifying an install location 162 mTask.run(TEST_USER_ID); 163 164 // THEN package installed is invoked with an install observer 165 IntentSender observer = verifyPackageInstalled( 166 INSTALL_REPLACE_EXISTING | INSTALL_ALLOW_TEST); 167 168 // WHEN the package installed callback is invoked with success 169 Intent fillIn = new Intent(); 170 fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, TEST_PACKAGE_NAME); 171 fillIn.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_SUCCESS); 172 observer.sendIntent(getContext(), 0, fillIn, null, null); 173 174 // THEN we receive a success callback 175 verify(mCallback, timeout(TIMEOUT)).onSuccess(mTask); 176 verifyNoMoreInteractions(mCallback); 177 } 178 179 180 @SmallTest testInstallFailedVersionDowngrade()181 public void testInstallFailedVersionDowngrade() throws Exception { 182 // GIVEN a package was downloaded to TEST_LOCATION 183 when(mDownloadPackageTask.getDownloadedPackageLocation()).thenReturn(mTestPackageLocation); 184 185 // WHEN running the InstallPackageTask with a package already at a higher version 186 mTask.run(TEST_USER_ID); 187 188 // THEN package installed is invoked with an install observer 189 IntentSender observer = verifyPackageInstalled(INSTALL_REPLACE_EXISTING); 190 191 // WHEN the package installed callback is invoked with version downgrade error 192 Intent fillIn = new Intent(); 193 fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, TEST_PACKAGE_NAME); 194 fillIn.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE); 195 fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, 196 PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE); 197 observer.sendIntent(getContext(), 0, fillIn, null, null); 198 199 // THEN we get a success callback, because an existing version of the DPC is present 200 verify(mCallback, timeout(TIMEOUT)).onSuccess(mTask); 201 verifyNoMoreInteractions(mCallback); 202 } 203 204 @SmallTest testInstallFailedOtherError()205 public void testInstallFailedOtherError() throws Exception { 206 // GIVEN a package was downloaded to TEST_LOCATION 207 when(mDownloadPackageTask.getDownloadedPackageLocation()).thenReturn(mTestPackageLocation); 208 209 // WHEN running the InstallPackageTask with a package already at a higher version 210 mTask.run(TEST_USER_ID); 211 212 // THEN package installed is invoked with an install observer 213 IntentSender observer = verifyPackageInstalled(INSTALL_REPLACE_EXISTING); 214 215 // WHEN the package installed callback is invoked with version invalid apk error 216 Intent fillIn = new Intent(); 217 fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, TEST_PACKAGE_NAME); 218 fillIn.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE); 219 fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, 220 PackageManager.INSTALL_FAILED_INVALID_APK); 221 observer.sendIntent(getContext(), 0, fillIn, null, null); 222 223 // THEN we get a success callback, because an existing version of the DPC is present 224 verify(mCallback, timeout(TIMEOUT)).onError(mTask, ERROR_INSTALLATION_FAILED); 225 verifyNoMoreInteractions(mCallback); 226 } 227 228 @SmallTest testDifferentPackageName()229 public void testDifferentPackageName() throws Exception { 230 // GIVEN a package was downloaded to TEST_LOCATION 231 when(mDownloadPackageTask.getDownloadedPackageLocation()).thenReturn(mTestPackageLocation); 232 233 // WHEN running the InstallPackageTask with a package already at a higher version 234 mTask.run(TEST_USER_ID); 235 236 // THEN package installed is invoked with an install observer 237 IntentSender observer = verifyPackageInstalled(INSTALL_REPLACE_EXISTING); 238 239 // WHEN the package installed callback is invoked with the wrong package 240 Intent fillIn = new Intent(); 241 fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, OTHER_PACKAGE_NAME); 242 observer.sendIntent(getContext(), 0, fillIn, null, null); 243 244 // THEN we get a success callback, because the wrong package name 245 verify(mCallback, timeout(TIMEOUT)).onError(mTask, ERROR_PACKAGE_INVALID); 246 verifyNoMoreInteractions(mCallback); 247 } 248 verifyPackageInstalled(int installFlags)249 private IntentSender verifyPackageInstalled(int installFlags) throws IOException { 250 ArgumentCaptor<PackageInstaller.SessionParams> paramsCaptor 251 = ArgumentCaptor.forClass(PackageInstaller.SessionParams.class); 252 ArgumentCaptor<byte[]> fileContentCaptor = ArgumentCaptor.forClass(byte[].class); 253 254 verify(mPackageInstaller).createSession(paramsCaptor.capture()); 255 assertEquals(installFlags, paramsCaptor.getValue().installFlags); 256 verify(mSessionWriteStream).write(fileContentCaptor.capture(), eq(0), 257 eq(APK_CONTENT.length)); 258 assertTrue(Arrays.equals(APK_CONTENT, 259 Arrays.copyOf(fileContentCaptor.getValue(), APK_CONTENT.length))); 260 261 ArgumentCaptor<IntentSender> intentSenderCaptor 262 = ArgumentCaptor.forClass(IntentSender.class); 263 264 // THEN package installation was started and we will receive a status callback 265 verify(mSession).commit(intentSenderCaptor.capture()); 266 return intentSenderCaptor.getValue(); 267 } 268 } 269