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.internal.content.res; 18 19 import static org.mockito.ArgumentMatchers.any; 20 import static org.mockito.Mockito.doAnswer; 21 import static org.mockito.Mockito.doReturn; 22 import static org.mockito.Mockito.when; 23 24 import android.content.pm.parsing.ParsingPackageRead; 25 import android.os.Build; 26 import android.util.ArrayMap; 27 28 import com.android.internal.content.om.OverlayConfig.PackageProvider; 29 import com.android.internal.content.om.OverlayScanner; 30 import com.android.internal.content.om.OverlayScanner.ParsedOverlayInfo; 31 32 import org.junit.Assert; 33 import org.junit.rules.TestRule; 34 import org.junit.runner.Description; 35 import org.junit.runners.model.Statement; 36 import org.mockito.Mockito; 37 import org.mockito.invocation.InvocationOnMock; 38 39 import java.io.File; 40 import java.io.IOException; 41 import java.util.Map; 42 import java.util.function.BiConsumer; 43 import java.util.function.Supplier; 44 45 /** 46 * A {@link TestRule} that runs a test case twice. First, the test case runs with a non-null 47 * {@link OverlayScanner} as if the zygote process is scanning the overlay packages 48 * and parsing configuration files. The test case then runs with a non-null 49 * {@link PackageProvider} as if the system server is parsing configuration files. 50 * 51 * This simulates what will happen on device. If an exception would be thrown in the zygote, then 52 * the exception should be thrown in the first run of the test case. 53 */ 54 public class OverlayConfigIterationRule implements TestRule { 55 56 enum Iteration { 57 ZYGOTE, 58 SYSTEM_SERVER, 59 } 60 61 private final ArrayMap<File, ParsedOverlayInfo> mOverlayStubResults = new ArrayMap<>(); 62 private Supplier<OverlayScanner> mOverlayScanner; 63 private PackageProvider mPkgProvider; 64 private Iteration mIteration; 65 66 /** 67 * Mocks the parsing of the file to make it appear to the scanner that the file is a valid 68 * overlay APK. 69 **/ addOverlay(File path, String packageName, String targetPackage, int targetSdkVersion, boolean isStatic, int priority)70 void addOverlay(File path, String packageName, String targetPackage, int targetSdkVersion, 71 boolean isStatic, int priority) { 72 try { 73 final File canonicalPath = new File(path.getCanonicalPath()); 74 mOverlayStubResults.put(canonicalPath, new ParsedOverlayInfo( 75 packageName, targetPackage, targetSdkVersion, isStatic, priority, 76 canonicalPath)); 77 } catch (IOException e) { 78 Assert.fail("Failed to add overlay " + e); 79 } 80 } 81 addOverlay(File path, String packageName)82 void addOverlay(File path, String packageName) { 83 addOverlay(path, packageName, "target"); 84 } 85 addOverlay(File path, String packageName, String targetPackage)86 void addOverlay(File path, String packageName, String targetPackage) { 87 addOverlay(path, packageName, targetPackage, Build.VERSION_CODES.CUR_DEVELOPMENT); 88 } 89 addOverlay(File path, String packageName, String targetPackage, int targetSdkVersion)90 void addOverlay(File path, String packageName, String targetPackage, int targetSdkVersion) { 91 addOverlay(path, packageName, targetPackage, targetSdkVersion, false, 0); 92 } 93 94 /** Retrieves the {@link OverlayScanner} for the current run of the test. */ getScannerFactory()95 Supplier<OverlayScanner> getScannerFactory() { 96 return mOverlayScanner; 97 } 98 99 /** Retrieves the {@link PackageProvider} for the current run of the test. */ getPackageProvider()100 PackageProvider getPackageProvider() { 101 return mPkgProvider; 102 } 103 104 /** Retrieves the current iteration of the test. */ getIteration()105 Iteration getIteration() { 106 return mIteration; 107 } 108 109 110 @Override apply(Statement base, Description description)111 public Statement apply(Statement base, Description description) { 112 return new Statement() { 113 @Override 114 public void evaluate() throws Throwable { 115 // Run the test once as if the zygote process is scanning the overlay packages 116 // and parsing configuration files. 117 mOverlayScanner = () -> { 118 OverlayScanner scanner = Mockito.spy(new OverlayScanner()); 119 for (Map.Entry<File, ParsedOverlayInfo> overlay : 120 mOverlayStubResults.entrySet()) { 121 doReturn(overlay.getValue()).when(scanner) 122 .parseOverlayManifest(overlay.getKey()); 123 } 124 return scanner; 125 }; 126 mPkgProvider = null; 127 mIteration = Iteration.ZYGOTE; 128 base.evaluate(); 129 130 // Run the test once more (if the first test did not throw an exception) as if 131 // the system server is parsing the configuration files and using PackageManager to 132 // retrieving information of overlays. 133 mOverlayScanner = null; 134 mPkgProvider = Mockito.mock(PackageProvider.class); 135 mIteration = Iteration.SYSTEM_SERVER; 136 doAnswer((InvocationOnMock invocation) -> { 137 final Object[] args = invocation.getArguments(); 138 final BiConsumer<ParsingPackageRead, Boolean> f = 139 (BiConsumer<ParsingPackageRead, Boolean>) args[0]; 140 for (Map.Entry<File, ParsedOverlayInfo> overlay : 141 mOverlayStubResults.entrySet()) { 142 final ParsingPackageRead a = Mockito.mock(ParsingPackageRead.class); 143 final ParsedOverlayInfo info = overlay.getValue(); 144 when(a.getPackageName()).thenReturn(info.packageName); 145 when(a.getOverlayTarget()).thenReturn(info.targetPackageName); 146 when(a.getTargetSdkVersion()).thenReturn(info.targetSdkVersion); 147 when(a.isOverlayIsStatic()).thenReturn(info.isStatic); 148 when(a.getOverlayPriority()).thenReturn(info.priority); 149 when(a.getBaseApkPath()).thenReturn(info.path.getPath()); 150 f.accept(a, !info.path.getPath().contains("data/overlay")); 151 } 152 return null; 153 }).when(mPkgProvider).forEachPackage(any()); 154 155 base.evaluate(); 156 } 157 }; 158 } 159 } 160 161 162