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