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