1 /*
2  * Copyright (C) 2020 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.om;
18 
19 import static com.android.server.om.OverlayManagerServiceImpl.OperationFailedException;
20 
21 import static org.junit.Assert.assertEquals;
22 import static org.mockito.ArgumentMatchers.any;
23 import static org.mockito.Mockito.mock;
24 import static org.mockito.Mockito.when;
25 
26 import android.annotation.NonNull;
27 import android.content.Intent;
28 import android.content.om.OverlayIdentifier;
29 import android.content.om.OverlayInfo;
30 import android.content.om.OverlayInfo.State;
31 import android.content.om.OverlayableInfo;
32 import android.content.pm.UserPackage;
33 import android.os.FabricatedOverlayInfo;
34 import android.os.FabricatedOverlayInternal;
35 import android.text.TextUtils;
36 import android.util.ArrayMap;
37 import android.util.ArraySet;
38 
39 import androidx.annotation.Nullable;
40 
41 import com.android.internal.content.om.OverlayConfig;
42 import com.android.server.pm.pkg.AndroidPackage;
43 import com.android.server.pm.pkg.AndroidPackageSplit;
44 import com.android.server.pm.pkg.PackageState;
45 
46 import org.junit.Assert;
47 import org.junit.Before;
48 import org.mockito.Mockito;
49 
50 import java.util.ArrayList;
51 import java.util.Arrays;
52 import java.util.Collections;
53 import java.util.List;
54 import java.util.Map;
55 import java.util.Set;
56 
57 /** Base class for creating {@link OverlayManagerServiceImplTests} tests. */
58 class OverlayManagerServiceImplTestsBase {
59     private OverlayManagerServiceImpl mImpl;
60     private FakeDeviceState mState;
61     private FakePackageManagerHelper mPackageManager;
62     private FakeIdmapDaemon mIdmapDaemon;
63     private OverlayConfig mOverlayConfig;
64     private String mConfigSignaturePackageName;
65 
66     @Before
setUp()67     public void setUp() {
68         mState = new FakeDeviceState();
69         mPackageManager = new FakePackageManagerHelper(mState);
70         mIdmapDaemon = new FakeIdmapDaemon(mState);
71         mOverlayConfig = mock(OverlayConfig.class);
72         when(mOverlayConfig.getPriority(any())).thenReturn(OverlayConfig.DEFAULT_PRIORITY);
73         when(mOverlayConfig.isEnabled(any())).thenReturn(false);
74         when(mOverlayConfig.isMutable(any())).thenReturn(true);
75         reinitializeImpl();
76     }
77 
reinitializeImpl()78     void reinitializeImpl() {
79         mImpl = new OverlayManagerServiceImpl(mPackageManager,
80                 new IdmapManager(mIdmapDaemon, mPackageManager),
81                 new OverlayManagerSettings(),
82                 mOverlayConfig,
83                 new String[0]);
84     }
85 
getImpl()86     OverlayManagerServiceImpl getImpl() {
87         return mImpl;
88     }
89 
getIdmapd()90     FakeIdmapDaemon getIdmapd() {
91         return mIdmapDaemon;
92     }
93 
getState()94     FakeDeviceState getState() {
95         return mState;
96     }
97 
setConfigSignaturePackageName(String packageName)98     void setConfigSignaturePackageName(String packageName) {
99         mConfigSignaturePackageName = packageName;
100     }
101 
assertState(@tate int expected, final OverlayIdentifier overlay, int userId)102     void assertState(@State int expected, final OverlayIdentifier overlay, int userId) {
103         final OverlayInfo info = mImpl.getOverlayInfo(overlay, userId);
104         if (info == null) {
105             throw new IllegalStateException("overlay '" + overlay + "' not installed");
106         }
107         final String msg = String.format("expected %s but was %s:",
108                 OverlayInfo.stateToString(expected), OverlayInfo.stateToString(info.state));
109         assertEquals(msg, expected, info.state);
110     }
111 
assertOverlayInfoForTarget(final String targetPackageName, int userId, OverlayInfo... overlayInfos)112     void assertOverlayInfoForTarget(final String targetPackageName, int userId,
113             OverlayInfo... overlayInfos) {
114         final List<OverlayInfo> expected =
115                 mImpl.getOverlayInfosForTarget(targetPackageName, userId);
116         final List<OverlayInfo> actual = Arrays.asList(overlayInfos);
117         assertEquals(expected, actual);
118     }
119 
app(String packageName)120     FakeDeviceState.PackageBuilder app(String packageName) {
121         return new FakeDeviceState.PackageBuilder(packageName, null /* targetPackageName */,
122                 null /* targetOverlayableName */, "data");
123     }
124 
target(String packageName)125     FakeDeviceState.PackageBuilder target(String packageName) {
126         return new FakeDeviceState.PackageBuilder(packageName, null /* targetPackageName */,
127                 null /* targetOverlayableName */, "");
128     }
129 
overlay(String packageName, String targetPackageName)130     FakeDeviceState.PackageBuilder overlay(String packageName, String targetPackageName) {
131         return overlay(packageName, targetPackageName, null /* targetOverlayableName */);
132     }
133 
overlay(String packageName, String targetPackageName, String targetOverlayableName)134     FakeDeviceState.PackageBuilder overlay(String packageName, String targetPackageName,
135             String targetOverlayableName) {
136         return new FakeDeviceState.PackageBuilder(packageName, targetPackageName,
137                 targetOverlayableName, "");
138     }
139 
addPackage(FakeDeviceState.PackageBuilder pkg, int userId)140     void addPackage(FakeDeviceState.PackageBuilder pkg, int userId) {
141         mState.add(pkg, userId);
142     }
143 
144     enum ConfigState {
145         IMMUTABLE_DISABLED,
146         IMMUTABLE_ENABLED,
147         MUTABLE_DISABLED,
148         MUTABLE_ENABLED
149     }
150 
configureSystemOverlay(@onNull String packageName, @NonNull ConfigState state, int priority)151     void configureSystemOverlay(@NonNull String packageName, @NonNull ConfigState state,
152             int priority) {
153         final boolean mutable = state == ConfigState.MUTABLE_DISABLED
154                 || state == ConfigState.MUTABLE_ENABLED;
155         final boolean enabled = state == ConfigState.IMMUTABLE_ENABLED
156                 || state == ConfigState.MUTABLE_ENABLED;
157         when(mOverlayConfig.getPriority(packageName)).thenReturn(priority);
158         when(mOverlayConfig.isEnabled(packageName)).thenReturn(enabled);
159         when(mOverlayConfig.isMutable(packageName)).thenReturn(mutable);
160     }
161 
162     /**
163      * Adds the package to the device.
164      *
165      * This corresponds to when the OMS receives the
166      * {@link Intent#ACTION_PACKAGE_ADDED} broadcast.
167      *
168      * @throws IllegalStateException if the package is currently installed
169      */
installAndAssert(@onNull FakeDeviceState.PackageBuilder pkg, int userId, @NonNull Set<UserPackage> onAddedUpdatedPackages)170     void installAndAssert(@NonNull FakeDeviceState.PackageBuilder pkg, int userId,
171             @NonNull Set<UserPackage> onAddedUpdatedPackages)
172             throws OperationFailedException {
173         if (mState.select(pkg.packageName, userId) != null) {
174             throw new IllegalStateException("package " + pkg.packageName + " already installed");
175         }
176         mState.add(pkg, userId);
177         assertEquals(onAddedUpdatedPackages, mImpl.onPackageAdded(pkg.packageName, userId));
178     }
179 
180     /**
181      * Begins upgrading the package.
182      *
183      * This corresponds to when the OMS receives the
184      * {@link Intent#ACTION_PACKAGE_REMOVED} broadcast with the
185      * {@link Intent#EXTRA_REPLACING} extra and then receives the
186      * {@link Intent#ACTION_PACKAGE_ADDED} broadcast with the
187      * {@link Intent#EXTRA_REPLACING} extra.
188      *
189      * @throws IllegalStateException if the package is not currently installed
190      */
upgradeAndAssert(FakeDeviceState.PackageBuilder pkg, int userId, @NonNull Set<UserPackage> onReplacingUpdatedPackages, @NonNull Set<UserPackage> onReplacedUpdatedPackages)191     void upgradeAndAssert(FakeDeviceState.PackageBuilder pkg, int userId,
192             @NonNull Set<UserPackage> onReplacingUpdatedPackages,
193             @NonNull Set<UserPackage> onReplacedUpdatedPackages)
194             throws OperationFailedException {
195         final FakeDeviceState.Package replacedPackage = mState.select(pkg.packageName, userId);
196         if (replacedPackage == null) {
197             throw new IllegalStateException("package " + pkg.packageName + " not installed");
198         }
199 
200         assertEquals(onReplacingUpdatedPackages, mImpl.onPackageReplacing(pkg.packageName,
201                 /* systemUpdateUninstall */ false, userId));
202         mState.add(pkg, userId);
203         assertEquals(onReplacedUpdatedPackages, mImpl.onPackageReplaced(pkg.packageName, userId));
204     }
205 
206     /**
207      * Begins downgrading the package. Usually used simulating a system uninstall of its /data
208      * variant.
209      *
210      * This corresponds to when the OMS receives the
211      * {@link Intent#ACTION_PACKAGE_REMOVED} broadcast with the
212      * {@link Intent#EXTRA_REPLACING} and {@link Intent#EXTRA_SYSTEM_UPDATE_UNINSTALL} extras
213      * and then receives the {@link Intent#ACTION_PACKAGE_ADDED} broadcast with the
214      * {@link Intent#EXTRA_REPLACING} extra.
215      *
216      * @throws IllegalStateException if the package is not currently installed
217      */
downgradeAndAssert(FakeDeviceState.PackageBuilder pkg, int userId, @NonNull Set<UserPackage> onReplacingUpdatedPackages, @NonNull Set<UserPackage> onReplacedUpdatedPackages)218     void downgradeAndAssert(FakeDeviceState.PackageBuilder pkg, int userId,
219             @NonNull Set<UserPackage> onReplacingUpdatedPackages,
220             @NonNull Set<UserPackage> onReplacedUpdatedPackages)
221             throws OperationFailedException {
222         final FakeDeviceState.Package replacedPackage = mState.select(pkg.packageName, userId);
223         if (replacedPackage == null) {
224             throw new IllegalStateException("package " + pkg.packageName + " not installed");
225         }
226 
227         assertEquals(onReplacingUpdatedPackages, mImpl.onPackageReplacing(pkg.packageName,
228                 /* systemUpdateUninstall */ true, userId));
229         mState.add(pkg, userId);
230         assertEquals(onReplacedUpdatedPackages, mImpl.onPackageReplaced(pkg.packageName, userId));
231     }
232 
233     /**
234      * Removes the package from the device.
235      *
236      * This corresponds to when the OMS receives the
237      * {@link Intent#ACTION_PACKAGE_REMOVED} broadcast.
238      *
239      * @throws IllegalStateException if the package is not currently installed
240      */
uninstallAndAssert(@onNull String packageName, int userId, @NonNull Set<UserPackage> onRemovedUpdatedPackages)241     void uninstallAndAssert(@NonNull String packageName, int userId,
242             @NonNull Set<UserPackage> onRemovedUpdatedPackages) {
243         final FakeDeviceState.Package pkg = mState.select(packageName, userId);
244         if (pkg == null) {
245             throw new IllegalStateException("package " + packageName + " not installed");
246         }
247         mState.remove(pkg.packageName);
248         assertEquals(onRemovedUpdatedPackages, mImpl.onPackageRemoved(pkg.packageName, userId));
249     }
250 
251     /** Represents the state of packages installed on a fake device. */
252     static class FakeDeviceState {
253         private ArrayMap<String, Package> mPackages = new ArrayMap<>();
254 
add(PackageBuilder pkgBuilder, int userId)255         void add(PackageBuilder pkgBuilder, int userId) {
256             final Package pkg = pkgBuilder.build();
257             final Package previousPkg = select(pkg.packageName, userId);
258             mPackages.put(pkg.packageName, pkg);
259 
260             pkg.installedUserIds.add(userId);
261             if (previousPkg != null) {
262                 pkg.installedUserIds.addAll(previousPkg.installedUserIds);
263             }
264         }
265 
remove(String packageName)266         void remove(String packageName) {
267             mPackages.remove(packageName);
268         }
269 
uninstall(String packageName, int userId)270         void uninstall(String packageName, int userId) {
271             final Package pkg = mPackages.get(packageName);
272             if (pkg != null) {
273                 pkg.installedUserIds.remove(userId);
274             }
275         }
276 
select(String packageName, int userId)277         Package select(String packageName, int userId) {
278             final Package pkg = mPackages.get(packageName);
279             return pkg != null && pkg.installedUserIds.contains(userId) ? pkg : null;
280         }
281 
selectFromPath(String path)282         private Package selectFromPath(String path) {
283             return mPackages.values().stream()
284                     .filter(p -> p.apkPath.equals(path)).findFirst().orElse(null);
285         }
286 
287         static final class PackageBuilder {
288             private String packageName;
289             private String targetPackage;
290             private String certificate = "[default]";
291             private String partition;
292             private int version = 0;
293             private ArrayList<String> overlayableNames = new ArrayList<>();
294             private String targetOverlayableName;
295 
PackageBuilder(String packageName, String targetPackage, String targetOverlayableName, String partition)296             private PackageBuilder(String packageName, String targetPackage,
297                     String targetOverlayableName, String partition) {
298                 this.packageName = packageName;
299                 this.targetPackage = targetPackage;
300                 this.targetOverlayableName = targetOverlayableName;
301                 this.partition = partition;
302             }
303 
setCertificate(String certificate)304             PackageBuilder setCertificate(String certificate) {
305                 this.certificate = certificate;
306                 return this;
307             }
308 
addOverlayable(String overlayableName)309             PackageBuilder addOverlayable(String overlayableName) {
310                 overlayableNames.add(overlayableName);
311                 return this;
312             }
313 
setVersion(int version)314             PackageBuilder setVersion(int version) {
315                 this.version = version;
316                 return this;
317             }
318 
build()319             Package build() {
320                 String path = "";
321                 if (TextUtils.isEmpty(partition)) {
322                     if (targetPackage == null) {
323                         path = "/system/app";
324                     } else {
325                         path = "/vendor/overlay";
326                     }
327                 } else {
328                     String type = targetPackage == null ? "app" : "overlay";
329                     path = String.format("%s/%s", partition, type);
330                 }
331 
332                 final String apkPath = String.format("%s/%s/base.apk", path, packageName);
333                 final Package newPackage = new Package(packageName, targetPackage,
334                         targetOverlayableName, version, apkPath, certificate);
335                 newPackage.overlayableNames.addAll(overlayableNames);
336                 return newPackage;
337             }
338         }
339 
340         static final class Package {
341             final String packageName;
342             final String targetPackageName;
343             final String targetOverlayableName;
344             final int versionCode;
345             final String apkPath;
346             final String certificate;
347             final ArrayList<String> overlayableNames = new ArrayList<>();
348             private final ArraySet<Integer> installedUserIds = new ArraySet<>();
349 
Package(String packageName, String targetPackageName, String targetOverlayableName, int versionCode, String apkPath, String certificate)350             private Package(String packageName, String targetPackageName,
351                     String targetOverlayableName, int versionCode, String apkPath,
352                     String certificate) {
353                 this.packageName = packageName;
354                 this.targetPackageName = targetPackageName;
355                 this.targetOverlayableName = targetOverlayableName;
356                 this.versionCode = versionCode;
357                 this.apkPath = apkPath;
358                 this.certificate = certificate;
359             }
360 
361             @Nullable
getPackageForUser(int user)362             private PackageState getPackageForUser(int user) {
363                 if (!installedUserIds.contains(user)) {
364                     return null;
365                 }
366                 final AndroidPackage pkg = Mockito.mock(AndroidPackage.class);
367                 when(pkg.getPackageName()).thenReturn(packageName);
368                 when(pkg.getLongVersionCode()).thenReturn((long) versionCode);
369                 when(pkg.getOverlayTarget()).thenReturn(targetPackageName);
370                 when(pkg.getOverlayTargetOverlayableName()).thenReturn(targetOverlayableName);
371                 when(pkg.getOverlayCategory()).thenReturn("Fake-category-" + targetPackageName);
372                 var baseSplit = mock(AndroidPackageSplit.class);
373                 when(baseSplit.getPath()).thenReturn(apkPath);
374                 when(pkg.getSplits()).thenReturn(List.of(baseSplit));
375 
376                 var pkgState = Mockito.mock(PackageState.class);
377                 when(pkgState.getPackageName()).thenReturn(packageName);
378                 when(pkgState.getAndroidPackage()).thenReturn(pkg);
379                 return pkgState;
380             }
381         }
382     }
383 
384     final class FakePackageManagerHelper implements PackageManagerHelper {
385         private final FakeDeviceState mState;
386 
FakePackageManagerHelper(FakeDeviceState state)387         private FakePackageManagerHelper(FakeDeviceState state) {
388             mState = state;
389         }
390 
391         @NonNull
392         @Override
initializeForUser(int userId)393         public ArrayMap<String, PackageState> initializeForUser(int userId) {
394             final ArrayMap<String, PackageState> packages = new ArrayMap<>();
395             mState.mPackages.forEach((key, value) -> {
396                 final PackageState pkg = value.getPackageForUser(userId);
397                 if (pkg != null) {
398                     packages.put(key, pkg);
399                 }
400             });
401             return packages;
402         }
403 
404         @Nullable
405         @Override
getPackageStateForUser(@onNull String packageName, int userId)406         public PackageState getPackageStateForUser(@NonNull String packageName, int userId) {
407             final FakeDeviceState.Package pkgState = mState.select(packageName, userId);
408             return pkgState == null ? null : pkgState.getPackageForUser(userId);
409         }
410 
411         @Override
isInstantApp(@onNull String packageName, int userId)412         public boolean isInstantApp(@NonNull String packageName, int userId) {
413             return false;
414         }
415 
416         @Override
signaturesMatching(@onNull String packageName1, @NonNull String packageName2, int userId)417         public boolean signaturesMatching(@NonNull String packageName1,
418                 @NonNull String packageName2, int userId) {
419             final FakeDeviceState.Package pkg1 = mState.select(packageName1, userId);
420             final FakeDeviceState.Package pkg2 = mState.select(packageName2, userId);
421             return pkg1 != null && pkg2 != null && pkg1.certificate.equals(pkg2.certificate);
422         }
423 
424         @Override
getConfigSignaturePackage()425         public @NonNull String getConfigSignaturePackage() {
426             return mConfigSignaturePackageName;
427         }
428 
429         @Nullable
430         @Override
getOverlayableForTarget(@onNull String packageName, @NonNull String targetOverlayableName, int userId)431         public OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
432                 @NonNull String targetOverlayableName, int userId) {
433             final FakeDeviceState.Package pkg = mState.select(packageName, userId);
434             if (pkg == null || !pkg.overlayableNames.contains(targetOverlayableName)) {
435                 return null;
436             }
437             return new OverlayableInfo(targetOverlayableName, null /* actor */);
438         }
439 
440         @Nullable
441         @Override
getPackagesForUid(int uid)442         public String[] getPackagesForUid(int uid) {
443             throw new UnsupportedOperationException();
444         }
445 
446         @NonNull
447         @Override
getNamedActors()448         public Map<String, Map<String, String>> getNamedActors() {
449             return Collections.emptyMap();
450         }
451 
452         @Override
doesTargetDefineOverlayable(String targetPackageName, int userId)453         public boolean doesTargetDefineOverlayable(String targetPackageName, int userId) {
454             final FakeDeviceState.Package pkg = mState.select(targetPackageName, userId);
455             return pkg != null && pkg.overlayableNames.contains(targetPackageName);
456         }
457 
458         @Override
enforcePermission(String permission, String message)459         public void enforcePermission(String permission, String message) throws SecurityException {
460             throw new UnsupportedOperationException();
461         }
462     }
463 
464     static class FakeIdmapDaemon extends IdmapDaemon {
465         private final FakeDeviceState mState;
466         private final ArrayMap<String, IdmapHeader> mIdmapFiles = new ArrayMap<>();
467         private final ArrayMap<String, FabricatedOverlayInfo> mFabricatedOverlays =
468                 new ArrayMap<>();
469         private int mFabricatedAssetSeq = 0;
470 
FakeIdmapDaemon(FakeDeviceState state)471         FakeIdmapDaemon(FakeDeviceState state) {
472             this.mState = state;
473         }
474 
getCrc(@onNull final String path)475         private int getCrc(@NonNull final String path) {
476             final FakeDeviceState.Package pkg = mState.selectFromPath(path);
477             Assert.assertNotNull("path = " + path, pkg);
478             return pkg.versionCode;
479         }
480 
481         @Override
createIdmap(String targetPath, String overlayPath, String overlayName, int policies, boolean enforce, int userId)482         String createIdmap(String targetPath, String overlayPath, String overlayName,
483                 int policies, boolean enforce, int userId) {
484             mIdmapFiles.put(overlayPath, new IdmapHeader(getCrc(targetPath),
485                     getCrc(overlayPath), targetPath, overlayName, policies, enforce));
486             return overlayPath;
487         }
488 
489         @Override
removeIdmap(String overlayPath, int userId)490         boolean removeIdmap(String overlayPath, int userId) {
491             return mIdmapFiles.remove(overlayPath) != null;
492         }
493 
494         @Override
verifyIdmap(String targetPath, String overlayPath, String overlayName, int policies, boolean enforce, int userId)495         boolean verifyIdmap(String targetPath, String overlayPath, String overlayName, int policies,
496                 boolean enforce, int userId) {
497             final IdmapHeader idmap = mIdmapFiles.get(overlayPath);
498             if (idmap == null) {
499                 return false;
500             }
501             return idmap.isUpToDate(getCrc(targetPath), getCrc(overlayPath), targetPath, policies,
502                     enforce);
503         }
504 
505         @Override
idmapExists(String overlayPath, int userId)506         boolean idmapExists(String overlayPath, int userId) {
507             return mIdmapFiles.containsKey(overlayPath);
508         }
509 
510         @Override
createFabricatedOverlay(@onNull FabricatedOverlayInternal overlay)511         FabricatedOverlayInfo createFabricatedOverlay(@NonNull FabricatedOverlayInternal overlay) {
512             final String path = Integer.toString(mFabricatedAssetSeq++);
513             final FabricatedOverlayInfo info = new FabricatedOverlayInfo();
514             info.path = path;
515             info.overlayName = overlay.overlayName;
516             info.packageName = overlay.packageName;
517             info.targetPackageName = overlay.targetPackageName;
518             info.targetOverlayable = overlay.targetOverlayable;
519             mFabricatedOverlays.put(path, info);
520             return info;
521         }
522 
523         @Override
deleteFabricatedOverlay(@onNull String path)524         boolean deleteFabricatedOverlay(@NonNull String path) {
525             return mFabricatedOverlays.remove(path) != null;
526         }
527 
528         @Override
getFabricatedOverlayInfos()529         List<FabricatedOverlayInfo> getFabricatedOverlayInfos() {
530             return new ArrayList<>(mFabricatedOverlays.values());
531         }
532 
getIdmap(String overlayPath)533         IdmapHeader getIdmap(String overlayPath) {
534             return mIdmapFiles.get(overlayPath);
535         }
536 
537         static class IdmapHeader {
538             private final int targetCrc;
539             private final int overlayCrc;
540             final String targetPath;
541             final String overlayName;
542             final int policies;
543             final boolean enforceOverlayable;
544 
IdmapHeader(int targetCrc, int overlayCrc, String targetPath, String overlayName, int policies, boolean enforceOverlayable)545             private IdmapHeader(int targetCrc, int overlayCrc, String targetPath,
546                     String overlayName, int policies, boolean enforceOverlayable) {
547                 this.targetCrc = targetCrc;
548                 this.overlayCrc = overlayCrc;
549                 this.targetPath = targetPath;
550                 this.overlayName = overlayName;
551                 this.policies = policies;
552                 this.enforceOverlayable = enforceOverlayable;
553             }
554 
isUpToDate(int expectedTargetCrc, int expectedOverlayCrc, String expectedTargetPath, int expectedPolicies, boolean expectedEnforceOverlayable)555             private boolean isUpToDate(int expectedTargetCrc, int expectedOverlayCrc,
556                     String expectedTargetPath, int expectedPolicies,
557                     boolean expectedEnforceOverlayable) {
558                 return expectedTargetCrc == targetCrc && expectedOverlayCrc == overlayCrc
559                         && expectedTargetPath.equals(targetPath) && expectedPolicies == policies
560                         && expectedEnforceOverlayable == enforceOverlayable;
561             }
562         }
563     }
564 }
565