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