1 /* 2 * Copyright (C) 2023 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.server.rollback; 18 19 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; 20 21 import static com.google.common.truth.Truth.assertThat; 22 23 import static org.junit.Assert.assertEquals; 24 import static org.junit.Assert.assertFalse; 25 import static org.junit.Assert.assertTrue; 26 import static org.mockito.Mockito.spy; 27 import static org.mockito.Mockito.when; 28 29 import android.content.Context; 30 import android.content.pm.ApplicationInfo; 31 import android.content.pm.PackageManager; 32 import android.content.pm.VersionedPackage; 33 import android.content.rollback.PackageRollbackInfo; 34 import android.content.rollback.RollbackInfo; 35 import android.content.rollback.RollbackManager; 36 import android.util.Log; 37 import android.util.Xml; 38 39 import androidx.test.runner.AndroidJUnit4; 40 41 import com.android.dx.mockito.inline.extended.ExtendedMockito; 42 import com.android.server.PackageWatchdog; 43 import com.android.server.SystemConfig; 44 45 import org.junit.After; 46 import org.junit.Before; 47 import org.junit.Rule; 48 import org.junit.Test; 49 import org.junit.rules.TemporaryFolder; 50 import org.junit.runner.RunWith; 51 import org.mockito.Answers; 52 import org.mockito.Mock; 53 import org.mockito.MockitoSession; 54 import org.mockito.quality.Strictness; 55 import org.mockito.stubbing.Answer; 56 import org.xmlpull.v1.XmlPullParser; 57 58 import java.io.BufferedWriter; 59 import java.io.File; 60 import java.io.FileWriter; 61 import java.io.IOException; 62 import java.util.List; 63 import java.util.Scanner; 64 65 66 @RunWith(AndroidJUnit4.class) 67 public class RollbackPackageHealthObserverTest { 68 @Mock 69 private Context mMockContext; 70 @Mock(answer = Answers.RETURNS_DEEP_STUBS) 71 private PackageWatchdog mMockPackageWatchdog; 72 @Mock 73 RollbackManager mRollbackManager; 74 @Mock 75 RollbackInfo mRollbackInfo; 76 @Mock 77 PackageRollbackInfo mPackageRollbackInfo; 78 @Mock 79 PackageManager mMockPackageManager; 80 81 private MockitoSession mSession; 82 private static final String APP_A = "com.package.a"; 83 private static final String APP_B = "com.package.b"; 84 private static final long VERSION_CODE = 1L; 85 private static final String LOG_TAG = "RollbackPackageHealthObserverTest"; 86 87 private SystemConfig mSysConfig; 88 89 @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); 90 91 @Before setup()92 public void setup() { 93 mSysConfig = new SystemConfigTestClass(); 94 95 mSession = ExtendedMockito.mockitoSession() 96 .initMocks(this) 97 .strictness(Strictness.LENIENT) 98 .spyStatic(PackageWatchdog.class) 99 .startMocking(); 100 101 // Mock PackageWatchdog 102 doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog) 103 .when(() -> PackageWatchdog.getInstance(mMockContext)); 104 105 } 106 107 @After tearDown()108 public void tearDown() throws Exception { 109 mSession.finishMocking(); 110 } 111 112 /** 113 * Subclass of SystemConfig without running the constructor. 114 */ 115 private class SystemConfigTestClass extends SystemConfig { SystemConfigTestClass()116 SystemConfigTestClass() { 117 super(false); 118 } 119 } 120 121 @Test testHealthCheckLevels()122 public void testHealthCheckLevels() { 123 RollbackPackageHealthObserver observer = 124 spy(new RollbackPackageHealthObserver(mMockContext)); 125 VersionedPackage testFailedPackage = new VersionedPackage(APP_A, VERSION_CODE); 126 VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE); 127 128 when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); 129 130 // Crashes with no rollbacks available 131 assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0, 132 observer.onHealthCheckFailed(null, 133 PackageWatchdog.FAILURE_REASON_NATIVE_CRASH, 1)); 134 assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0, 135 observer.onHealthCheckFailed(null, 136 PackageWatchdog.FAILURE_REASON_APP_CRASH, 1)); 137 138 // Make the rollbacks available 139 when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(mRollbackInfo)); 140 when(mRollbackInfo.getPackages()).thenReturn(List.of(mPackageRollbackInfo)); 141 when(mPackageRollbackInfo.getVersionRolledBackFrom()).thenReturn(testFailedPackage); 142 143 // native crash 144 assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_30, 145 observer.onHealthCheckFailed(null, 146 PackageWatchdog.FAILURE_REASON_NATIVE_CRASH, 1)); 147 // non-native crash for the package 148 assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_30, 149 observer.onHealthCheckFailed(testFailedPackage, 150 PackageWatchdog.FAILURE_REASON_APP_CRASH, 1)); 151 // non-native crash for a different package 152 assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70, 153 observer.onHealthCheckFailed(secondFailedPackage, 154 PackageWatchdog.FAILURE_REASON_APP_CRASH, 1)); 155 assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70, 156 observer.onHealthCheckFailed(secondFailedPackage, 157 PackageWatchdog.FAILURE_REASON_APP_CRASH, 2)); 158 // Subsequent crashes when rollbacks have completed 159 when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of()); 160 assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0, 161 observer.onHealthCheckFailed(testFailedPackage, 162 PackageWatchdog.FAILURE_REASON_APP_CRASH, 3)); 163 } 164 165 @Test testIsPersistent()166 public void testIsPersistent() { 167 RollbackPackageHealthObserver observer = 168 spy(new RollbackPackageHealthObserver(mMockContext)); 169 assertTrue(observer.isPersistent()); 170 } 171 172 @Test testMayObservePackage_withoutAnyRollback()173 public void testMayObservePackage_withoutAnyRollback() { 174 RollbackPackageHealthObserver observer = 175 spy(new RollbackPackageHealthObserver(mMockContext)); 176 when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); 177 when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of()); 178 assertFalse(observer.mayObservePackage(APP_A)); 179 } 180 181 @Test testMayObservePackage_forPersistentApp()182 public void testMayObservePackage_forPersistentApp() 183 throws PackageManager.NameNotFoundException { 184 RollbackPackageHealthObserver observer = 185 spy(new RollbackPackageHealthObserver(mMockContext)); 186 ApplicationInfo info = new ApplicationInfo(); 187 info.flags = ApplicationInfo.FLAG_PERSISTENT | ApplicationInfo.FLAG_SYSTEM; 188 when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); 189 when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(mRollbackInfo)); 190 when(mRollbackInfo.getPackages()).thenReturn(List.of(mPackageRollbackInfo)); 191 when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); 192 when(mMockPackageManager.getApplicationInfo(APP_A, 0)).thenReturn(info); 193 assertTrue(observer.mayObservePackage(APP_A)); 194 } 195 196 @Test testMayObservePackage_forNonPersistentApp()197 public void testMayObservePackage_forNonPersistentApp() 198 throws PackageManager.NameNotFoundException { 199 RollbackPackageHealthObserver observer = 200 spy(new RollbackPackageHealthObserver(mMockContext)); 201 when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); 202 when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(mRollbackInfo)); 203 when(mRollbackInfo.getPackages()).thenReturn(List.of(mPackageRollbackInfo)); 204 when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); 205 when(mMockPackageManager.getApplicationInfo(APP_A, 0)) 206 .thenThrow(new PackageManager.NameNotFoundException()); 207 assertFalse(observer.mayObservePackage(APP_A)); 208 } 209 210 /** 211 * Test that isAutomaticRollbackDenied works correctly when packages that are not 212 * denied are sent. 213 */ 214 @Test isRollbackAllowedTest_false()215 public void isRollbackAllowedTest_false() throws IOException { 216 final String contents = 217 "<config>\n" 218 + " <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n" 219 + "</config>"; 220 final File folder = createTempSubfolder("folder"); 221 createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents); 222 223 readPermissions(folder, /* Grant all permission flags */ ~0); 224 225 assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig, 226 new VersionedPackage("com.test.package", 1))).isEqualTo(false); 227 } 228 229 /** 230 * Test that isAutomaticRollbackDenied works correctly when packages that are 231 * denied are sent. 232 */ 233 @Test isRollbackAllowedTest_true()234 public void isRollbackAllowedTest_true() throws IOException { 235 final String contents = 236 "<config>\n" 237 + " <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n" 238 + "</config>"; 239 final File folder = createTempSubfolder("folder"); 240 createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents); 241 242 readPermissions(folder, /* Grant all permission flags */ ~0); 243 244 assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig, 245 new VersionedPackage("com.android.vending", 1))).isEqualTo(true); 246 } 247 248 /** 249 * Test that isAutomaticRollbackDenied works correctly when no config is present 250 */ 251 @Test isRollbackAllowedTest_noConfig()252 public void isRollbackAllowedTest_noConfig() throws IOException { 253 final File folder = createTempSubfolder("folder"); 254 255 readPermissions(folder, /* Grant all permission flags */ ~0); 256 257 assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig, 258 new VersionedPackage("com.android.vending", 1))).isEqualTo(false); 259 } 260 261 /** 262 * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents. 263 * 264 * @param folder pre-existing subdirectory of mTemporaryFolder to put the file 265 * @param fileName name of the file (e.g. filename.xml) to create 266 * @param contents contents to write to the file 267 * @return the newly created file 268 */ createTempFile(File folder, String fileName, String contents)269 private File createTempFile(File folder, String fileName, String contents) 270 throws IOException { 271 File file = new File(folder, fileName); 272 BufferedWriter bw = new BufferedWriter(new FileWriter(file)); 273 bw.write(contents); 274 bw.close(); 275 276 // Print to logcat for test debugging. 277 Log.d(LOG_TAG, "Contents of file " + file.getAbsolutePath()); 278 Scanner input = new Scanner(file); 279 while (input.hasNextLine()) { 280 Log.d(LOG_TAG, input.nextLine()); 281 } 282 283 return file; 284 } 285 readPermissions(File libraryDir, int permissionFlag)286 private void readPermissions(File libraryDir, int permissionFlag) { 287 final XmlPullParser parser = Xml.newPullParser(); 288 mSysConfig.readPermissions(parser, libraryDir, permissionFlag); 289 } 290 291 /** 292 * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents. 293 * 294 * @param folderName subdirectory of mTemporaryFolder to put the file, creating if needed 295 * @return the folder 296 */ createTempSubfolder(String folderName)297 private File createTempSubfolder(String folderName) 298 throws IOException { 299 File folder = new File(mTemporaryFolder.getRoot(), folderName); 300 folder.mkdirs(); 301 return folder; 302 } 303 } 304