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