blob: 530c1158a2e0281b2553a19e61b963521a32c0c6 [file] [log] [blame]
Bookatz04d7ae52019-08-05 14:07:12 -07001/*
2 * Copyright (C) 2019 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
17package com.android.server.pm;
18
19import android.annotation.IntDef;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.annotation.UserIdInt;
Adam Bookatz1efd27cd2020-03-19 18:37:36 -070023import android.content.pm.PackageManager;
Bookatz04d7ae52019-08-05 14:07:12 -070024import android.content.pm.PackageManagerInternal;
Bookatz04d7ae52019-08-05 14:07:12 -070025import android.content.res.Resources;
26import android.os.SystemProperties;
27import android.os.UserHandle;
28import android.util.ArrayMap;
29import android.util.ArraySet;
felipeal60aabe62020-04-21 13:20:35 -070030import android.util.DebugUtils;
Bookatz04d7ae52019-08-05 14:07:12 -070031import android.util.Slog;
32
33import com.android.internal.annotations.VisibleForTesting;
felipeala2f45ef2020-04-15 08:39:36 -070034import com.android.internal.util.IndentingPrintWriter;
Bookatz04d7ae52019-08-05 14:07:12 -070035import com.android.server.LocalServices;
36import com.android.server.SystemConfig;
Winson5e0a1d52020-01-24 12:00:33 -080037import com.android.server.pm.parsing.pkg.AndroidPackage;
Bookatz04d7ae52019-08-05 14:07:12 -070038
39import java.io.PrintWriter;
40import java.lang.annotation.Retention;
41import java.lang.annotation.RetentionPolicy;
felipeala2f45ef2020-04-15 08:39:36 -070042import java.util.ArrayList;
Bookatza8c2d2a2019-11-13 10:10:18 -080043import java.util.Arrays;
felipeal60aabe62020-04-21 13:20:35 -070044import java.util.Collections;
felipeala2f45ef2020-04-15 08:39:36 -070045import java.util.List;
Bookatza8c2d2a2019-11-13 10:10:18 -080046import java.util.Map;
Bookatz04d7ae52019-08-05 14:07:12 -070047import java.util.Set;
48
49/**
50 * Responsible for un/installing system packages based on user type.
51 *
52 * <p>Uses the SystemConfig's install-in-user-type whitelist;
53 * see {@link SystemConfig#getAndClearPackageToUserTypeWhitelist} and
54 * {@link SystemConfig#getAndClearPackageToUserTypeBlacklist}.
55 *
56 * <p>If {@link #isEnforceMode()} is false, then all system packages are always installed for all
57 * users. The following applies when it is true.
58 *
Felipe Leme7d4d7282019-10-25 10:03:18 -070059 * <p>Any package can be in one of three states in the {@code SystemConfig} whitelist
Bookatz04d7ae52019-08-05 14:07:12 -070060 * <ol>
61 * <li>Explicitly blacklisted for a particular user type</li>
62 * <li>Explicitly whitelisted for a particular user type</li>
63 * <li>Not mentioned at all, for any user type (neither whitelisted nor blacklisted)</li>
64 * </ol>
Felipe Leme7d4d7282019-10-25 10:03:18 -070065 *
66 * <p>Blacklisting always takes precedence - if a package is blacklisted for a particular user,
Bookatz04d7ae52019-08-05 14:07:12 -070067 * it won't be installed on that type of user (even if it is also whitelisted for that user).
68 * Next comes whitelisting - if it is whitelisted for a particular user, it will be installed on
69 * that type of user (as long as it isn't blacklisted).
70 * Finally, if the package is not mentioned at all (i.e. neither whitelisted nor blacklisted for
71 * any user types) in the SystemConfig 'install-in-user-type' lists
72 * then:
73 * <ul>
74 * <li>If {@link #isImplicitWhitelistMode()}, the package is implicitly treated as whitelisted
Bookatzeac923e2019-12-17 12:30:20 -080075 * for <b>all</b> users</li>
76 * <li>Otherwise, if {@link #isImplicitWhitelistSystemMode()}, the package is implicitly treated
77 * as whitelisted for the <b>{@link UserHandle#USER_SYSTEM}</b> user (not other users),
78 * which is useful for local development purposes</li>
79 * <li>Otherwise, the package is implicitly treated as blacklisted for all users</li>
Bookatz04d7ae52019-08-05 14:07:12 -070080 * </ul>
Felipe Leme7d4d7282019-10-25 10:03:18 -070081 *
Adam Bookatz1efd27cd2020-03-19 18:37:36 -070082 * <p>Packages are only installed/uninstalled by this mechanism when a new user is created or during
83 * an update. In the case of updates:<ul>
84 * <li>new packages are (un)installed per the whitelist/blacklist</li>
85 * <li>pre-existing installed blacklisted packages are never uninstalled</li>
86 * <li>pre-existing not-installed whitelisted packages are only installed if the reason why they
87 * had been previously uninstalled was due to UserSystemPackageInstaller</li>
88 * </ul>
89 *
Felipe Leme7d4d7282019-10-25 10:03:18 -070090 * <p><b>NOTE:</b> the {@code SystemConfig} state is only updated on first boot or after a system
91 * update. So, to verify changes during development, you can emulate the latter by calling:
92 * <pre><code>
93 * adb shell setprop persist.pm.mock-upgrade true
94 * </code></pre>
Bookatz04d7ae52019-08-05 14:07:12 -070095 */
96class UserSystemPackageInstaller {
97 private static final String TAG = "UserManagerService";
98
99 /**
100 * System Property whether to only install system packages on a user if they're whitelisted for
101 * that user type. These are flags and can be freely combined.
102 * <ul>
Bookatzeac923e2019-12-17 12:30:20 -0800103 * <li> 0 - disable whitelist (install all system packages; no logging)</li>
104 * <li> 1 - enforce (only install system packages if they are whitelisted)</li>
Adam Bookatz34839142020-02-10 13:56:46 -0800105 * <li> 2 - log (log non-whitelisted packages)</li>
Bookatzeac923e2019-12-17 12:30:20 -0800106 * <li> 4 - for all users: implicitly whitelist any package not mentioned in the whitelist</li>
107 * <li> 8 - for SYSTEM: implicitly whitelist any package not mentioned in the whitelist</li>
108 * <li> 16 - ignore OTAs (don't install system packages during OTAs)</li>
109 * <li>-1 - use device default (as defined in res/res/values/config.xml)</li>
Bookatz04d7ae52019-08-05 14:07:12 -0700110 * </ul>
111 * Note: This list must be kept current with config_userTypePackageWhitelistMode in
112 * frameworks/base/core/res/res/values/config.xml
113 */
114 static final String PACKAGE_WHITELIST_MODE_PROP = "persist.debug.user.package_whitelist_mode";
felipeal60aabe62020-04-21 13:20:35 -0700115
116 // NOTE: flags below are public so they can used by DebugUtils.flagsToString. And this class
117 // itself is package-protected, so it doesn't matter...
118 public static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE = 0x00;
119 public static final int USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE = 0x01;
120 public static final int USER_TYPE_PACKAGE_WHITELIST_MODE_LOG = 0x02;
121 public static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST = 0x04;
122 public static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST_SYSTEM = 0x08;
123 public static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IGNORE_OTA = 0x10;
Bookatz04d7ae52019-08-05 14:07:12 -0700124 static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT = -1;
125
felipeal60aabe62020-04-21 13:20:35 -0700126 // Used by Shell command only
127 static final int USER_TYPE_PACKAGE_WHITELIST_MODE_NONE = -1000;
128
Bookatz04d7ae52019-08-05 14:07:12 -0700129 @IntDef(flag = true, prefix = "USER_TYPE_PACKAGE_WHITELIST_MODE_", value = {
130 USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE,
131 USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE,
Bookatz04d7ae52019-08-05 14:07:12 -0700132 USER_TYPE_PACKAGE_WHITELIST_MODE_LOG,
133 USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST,
Bookatz0274a6e2019-11-25 15:31:39 -0800134 USER_TYPE_PACKAGE_WHITELIST_MODE_IGNORE_OTA,
Bookatz04d7ae52019-08-05 14:07:12 -0700135 })
136 @Retention(RetentionPolicy.SOURCE)
137 public @interface PackageWhitelistMode {}
138
139 /**
Bookatza8c2d2a2019-11-13 10:10:18 -0800140 * Maps system package manifest names to a bitset representing (via {@link #getUserTypeMask})
141 * the user types on which they should be initially installed.
142 * <p>
143 * E.g. if package "pkg1" should be installed on "usertype_d", which is the user type for which
144 * {@link #getUserTypeMask}("usertype_d") returns (1 << 3)
145 * then mWhitelistedPackagesForUserTypes.get("pkg1") will be a Long whose
146 * bit in position 3 will equal 1.
147 * <p>
148 * Packages that are whitelisted, but then blacklisted so that they aren't to be installed on
Bookatz04d7ae52019-08-05 14:07:12 -0700149 * any user, are purposefully still present in this list.
150 */
Bookatza8c2d2a2019-11-13 10:10:18 -0800151 private final ArrayMap<String, Long> mWhitelistedPackagesForUserTypes;
Bookatz04d7ae52019-08-05 14:07:12 -0700152
153 private final UserManagerService mUm;
154
Bookatza8c2d2a2019-11-13 10:10:18 -0800155 /**
156 * Alphabetically sorted list of user types.
157 * Throughout this class, a long (functioning as a bitset) has its ith bit representing
158 * the user type stored in mUserTypes[i].
159 * mUserTypes cannot exceed Long.SIZE (since we are using long for our bitset).
160 */
161 private final String[] mUserTypes;
162
163 UserSystemPackageInstaller(UserManagerService um, ArrayMap<String, UserTypeDetails> userTypes) {
164 mUm = um;
165 mUserTypes = getAndSortKeysFromMap(userTypes);
166 if (mUserTypes.length > Long.SIZE) {
167 throw new IllegalArgumentException("Device contains " + userTypes.size()
168 + " user types. However, UserSystemPackageInstaller does not work if there are"
169 + " more than " + Long.SIZE + " user types.");
170 // UserSystemPackageInstaller could use a BitSet instead of Long in this case.
171 // But, currently, 64 user types is far beyond expectations, so we have not done so.
172 }
Felipe Leme1fbe9b52019-10-21 14:20:08 -0700173 mWhitelistedPackagesForUserTypes =
Bookatz04d7ae52019-08-05 14:07:12 -0700174 determineWhitelistedPackagesForUserTypes(SystemConfig.getInstance());
175 }
176
177 /** Constructor for testing purposes. */
178 @VisibleForTesting
Bookatza8c2d2a2019-11-13 10:10:18 -0800179 UserSystemPackageInstaller(UserManagerService ums, ArrayMap<String, Long> whitelist,
180 String[] sortedUserTypes) {
Bookatz04d7ae52019-08-05 14:07:12 -0700181 mUm = ums;
Bookatza8c2d2a2019-11-13 10:10:18 -0800182 mUserTypes = sortedUserTypes;
Felipe Leme1fbe9b52019-10-21 14:20:08 -0700183 mWhitelistedPackagesForUserTypes = whitelist;
Bookatz04d7ae52019-08-05 14:07:12 -0700184 }
185
186 /**
187 * During OTAs and first boot, install/uninstall all system packages for all users based on the
Bookatza8c2d2a2019-11-13 10:10:18 -0800188 * user's user type and the SystemConfig whitelist.
Bookatz04d7ae52019-08-05 14:07:12 -0700189 * We do NOT uninstall packages during an OTA though.
190 *
191 * This is responsible for enforcing the whitelist for pre-existing users (i.e. USER_SYSTEM);
192 * enforcement for new users is done when they are created in UserManagerService.createUser().
Adam Bookatz1efd27cd2020-03-19 18:37:36 -0700193 *
194 * @param preExistingPackages list of packages on the device prior to the upgrade. Cannot be
195 * null if isUpgrade is true.
Bookatz04d7ae52019-08-05 14:07:12 -0700196 */
Adam Bookatz1efd27cd2020-03-19 18:37:36 -0700197 boolean installWhitelistedSystemPackages(boolean isFirstBoot, boolean isUpgrade,
198 @Nullable ArraySet<String> preExistingPackages) {
Bookatz04d7ae52019-08-05 14:07:12 -0700199 final int mode = getWhitelistMode();
200 checkWhitelistedSystemPackages(mode);
Bookatz0274a6e2019-11-25 15:31:39 -0800201 final boolean isConsideredUpgrade = isUpgrade && !isIgnoreOtaMode(mode);
202 if (!isConsideredUpgrade && !isFirstBoot) {
203 return false;
204 }
205 if (isFirstBoot && !isEnforceMode(mode)) {
206 // Note that if !isEnforceMode, we nonetheless still install packages if isUpgrade
207 // in order to undo any previous non-installing. isFirstBoot lacks this requirement.
Bookatz04d7ae52019-08-05 14:07:12 -0700208 return false;
209 }
210 Slog.i(TAG, "Reviewing whitelisted packages due to "
Bookatz0274a6e2019-11-25 15:31:39 -0800211 + (isFirstBoot ? "[firstBoot]" : "") + (isConsideredUpgrade ? "[upgrade]" : ""));
Bookatz04d7ae52019-08-05 14:07:12 -0700212 final PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
213 // Install/uninstall system packages per user.
214 for (int userId : mUm.getUserIds()) {
215 final Set<String> userWhitelist = getInstallablePackagesForUserId(userId);
Winsone0756292020-01-31 12:21:54 -0800216 pmInt.forEachPackageSetting(pkgSetting -> {
217 AndroidPackage pkg = pkgSetting.pkg;
218 if (pkg == null || !pkg.isSystem()) {
Bookatz04d7ae52019-08-05 14:07:12 -0700219 return;
220 }
221 final boolean install =
Winson655a5b92019-10-23 10:49:32 -0700222 (userWhitelist == null || userWhitelist.contains(pkg.getPackageName()))
Winsone0756292020-01-31 12:21:54 -0800223 && !pkgSetting.getPkgState().isHiddenUntilInstalled();
Adam Bookatz1efd27cd2020-03-19 18:37:36 -0700224 if (pkgSetting.getInstalled(userId) == install
225 || !shouldChangeInstallationState(pkgSetting, install, userId, isFirstBoot,
226 isConsideredUpgrade, preExistingPackages)) {
227 return;
Bookatz04d7ae52019-08-05 14:07:12 -0700228 }
Adam Bookatz1efd27cd2020-03-19 18:37:36 -0700229 pkgSetting.setInstalled(install, userId);
230 pkgSetting.setUninstallReason(
231 install ? PackageManager.UNINSTALL_REASON_UNKNOWN :
232 PackageManager.UNINSTALL_REASON_USER_TYPE,
233 userId);
234 Slog.i(TAG, (install ? "Installed " : "Uninstalled ")
235 + pkg.getPackageName() + " for user " + userId);
Bookatz04d7ae52019-08-05 14:07:12 -0700236 });
237 }
238 return true;
239 }
240
241 /**
Adam Bookatz1efd27cd2020-03-19 18:37:36 -0700242 * Returns whether to proceed with install/uninstall for the given package.
243 * In particular, do not install a package unless it was only uninstalled due to the user type;
244 * and do not uninstall a package if it previously was installed (prior to the OTA).
245 *
246 * Should be called only within PackageManagerInternal.forEachPackageSetting() since it
247 * requires the LP lock.
248 *
249 * @param preOtaPkgs list of packages on the device prior to the upgrade.
250 * Cannot be null if isUpgrade is true.
251 */
252 private static boolean shouldChangeInstallationState(PackageSetting pkgSetting,
253 boolean install,
254 @UserIdInt int userId,
255 boolean isFirstBoot,
256 boolean isUpgrade,
257 @Nullable ArraySet<String> preOtaPkgs) {
258 if (install) {
259 // Only proceed with install if we are the only reason why it had been uninstalled.
260 return pkgSetting.getUninstallReason(userId)
261 == PackageManager.UNINSTALL_REASON_USER_TYPE;
262 } else {
263 // Only proceed with uninstall if the package is new to the device.
264 return isFirstBoot || (isUpgrade && !preOtaPkgs.contains(pkgSetting.name));
265 }
266 }
267
268 /**
Bookatz04d7ae52019-08-05 14:07:12 -0700269 * Checks whether the system packages and the mWhitelistedPackagesForUserTypes whitelist are
270 * in 1-to-1 correspondence.
271 */
272 private void checkWhitelistedSystemPackages(@PackageWhitelistMode int mode) {
273 if (!isLogMode(mode) && !isEnforceMode(mode)) {
274 return;
275 }
felipeal60aabe62020-04-21 13:20:35 -0700276 Slog.v(TAG, "Checking that all system packages are whitelisted.");
felipeala2f45ef2020-04-15 08:39:36 -0700277
felipeal60aabe62020-04-21 13:20:35 -0700278 // Check whether all whitelisted packages are indeed on the system.
279 final List<String> warnings = getPackagesWhitelistWarnings();
280 final int numberWarnings = warnings.size();
281 if (numberWarnings == 0) {
282 Slog.v(TAG, "checkWhitelistedSystemPackages(mode=" + modeToString(mode)
283 + ") has no warnings");
284 } else {
285 Slog.w(TAG, "checkWhitelistedSystemPackages(mode=" + modeToString(mode)
286 + ") has " + numberWarnings + " warnings:");
287 for (int i = 0; i < numberWarnings; i++) {
288 Slog.w(TAG, warnings.get(i));
felipeala2f45ef2020-04-15 08:39:36 -0700289 }
felipeal60aabe62020-04-21 13:20:35 -0700290 }
291
292 // Check whether all system packages are indeed whitelisted.
293 if (isImplicitWhitelistMode(mode) && !isLogMode(mode)) {
felipeala2f45ef2020-04-15 08:39:36 -0700294 return;
295 }
296
felipeal60aabe62020-04-21 13:20:35 -0700297 final List<String> errors = getPackagesWhitelistErrors(mode);
298 final int numberErrors = errors.size();
299
300 if (numberErrors == 0) {
301 Slog.v(TAG, "checkWhitelistedSystemPackages(mode=" + modeToString(mode)
302 + ") has no errors");
303 return;
304 }
305 Slog.e(TAG, "checkWhitelistedSystemPackages(mode=" + modeToString(mode) + ") has "
306 + numberErrors + " errors:");
307
felipeala2f45ef2020-04-15 08:39:36 -0700308 boolean doWtf = !isImplicitWhitelistMode(mode);
felipealc0f12862020-05-08 08:25:24 -0700309 for (int i = 0; i < numberErrors; i++) {
felipeal60aabe62020-04-21 13:20:35 -0700310 final String msg = errors.get(i);
311 if (doWtf) {
312 Slog.wtf(TAG, msg);
felipeala2f45ef2020-04-15 08:39:36 -0700313 } else {
felipeal60aabe62020-04-21 13:20:35 -0700314 Slog.e(TAG, msg);
felipeala2f45ef2020-04-15 08:39:36 -0700315 }
316 }
317 }
318
felipeala2f45ef2020-04-15 08:39:36 -0700319 /**
felipeal60aabe62020-04-21 13:20:35 -0700320 * Gets packages that are listed in the whitelist XML but are not present on the system image.
felipeala2f45ef2020-04-15 08:39:36 -0700321 */
322 @NonNull
felipeal60aabe62020-04-21 13:20:35 -0700323 private List<String> getPackagesWhitelistWarnings() {
felipeala2f45ef2020-04-15 08:39:36 -0700324 final Set<String> allWhitelistedPackages = getWhitelistedSystemPackages();
felipeal60aabe62020-04-21 13:20:35 -0700325 final List<String> warnings = new ArrayList<>();
felipeala2f45ef2020-04-15 08:39:36 -0700326 final PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
327
328 // Check whether all whitelisted packages are indeed on the system.
329 final String notPresentFmt = "%s is whitelisted but not present.";
330 final String notSystemFmt = "%s is whitelisted and present but not a system package.";
331 for (String pkgName : allWhitelistedPackages) {
332 final AndroidPackage pkg = pmInt.getPackage(pkgName);
333 if (pkg == null) {
felipeal60aabe62020-04-21 13:20:35 -0700334 warnings.add(String.format(notPresentFmt, pkgName));
felipeala2f45ef2020-04-15 08:39:36 -0700335 } else if (!pkg.isSystem()) {
felipeal60aabe62020-04-21 13:20:35 -0700336 warnings.add(String.format(notSystemFmt, pkgName));
felipeala2f45ef2020-04-15 08:39:36 -0700337 }
338 }
felipeal60aabe62020-04-21 13:20:35 -0700339 return warnings;
340 }
341
342 /**
343 * Gets packages that are not listed in the whitelist XMLs when they should be.
344 */
345 @NonNull
346 private List<String> getPackagesWhitelistErrors(@PackageWhitelistMode int mode) {
felipeala1c0dab2020-05-12 17:32:23 -0700347 if ((!isEnforceMode(mode) || isImplicitWhitelistMode(mode))) {
felipeal60aabe62020-04-21 13:20:35 -0700348 return Collections.emptyList();
349 }
350
351 final List<String> errors = new ArrayList<>();
352 final Set<String> allWhitelistedPackages = getWhitelistedSystemPackages();
353 final PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
felipeala2f45ef2020-04-15 08:39:36 -0700354
355 // Check whether all system packages are indeed whitelisted.
356 final String logMessageFmt = "System package %s is not whitelisted using "
357 + "'install-in-user-type' in SystemConfig for any user types!";
felipeala2f45ef2020-04-15 08:39:36 -0700358 pmInt.forEachPackage(pkg -> {
359 if (!pkg.isSystem()) return;
360 final String pkgName = pkg.getManifestPackageName();
361 if (!allWhitelistedPackages.contains(pkgName)) {
felipeal60aabe62020-04-21 13:20:35 -0700362 errors.add(String.format(logMessageFmt, pkgName));
Bookatz04d7ae52019-08-05 14:07:12 -0700363 }
364 });
felipeala2f45ef2020-04-15 08:39:36 -0700365
felipeal60aabe62020-04-21 13:20:35 -0700366 return errors;
Bookatz04d7ae52019-08-05 14:07:12 -0700367 }
368
369 /** Whether to only install system packages in new users for which they are whitelisted. */
370 boolean isEnforceMode() {
371 return isEnforceMode(getWhitelistMode());
372 }
373
374 /**
Bookatz0274a6e2019-11-25 15:31:39 -0800375 * Whether to ignore OTAs, and therefore not install missing system packages during OTAs.
376 * <p>Note:
377 * If in this mode, old system packages will not be installed on pre-existing users during OTAs.
378 * Any system packages that had not been installed at the time of the user's creation,
379 * due to {@link UserSystemPackageInstaller}'s previous actions, will therefore continue to
380 * remain uninstalled, even if the whitelist (or enforcement mode) now declares that they should
381 * be.
382 */
383 boolean isIgnoreOtaMode() {
384 return isIgnoreOtaMode(getWhitelistMode());
385 }
386
387 /**
Bookatz04d7ae52019-08-05 14:07:12 -0700388 * Whether to log a warning concerning potential problems with the user-type package whitelist.
389 */
390 boolean isLogMode() {
391 return isLogMode(getWhitelistMode());
392 }
393
394 /**
395 * Whether to treat all packages that are not mentioned at all in the whitelist to be implicitly
396 * whitelisted for all users.
397 */
398 boolean isImplicitWhitelistMode() {
399 return isImplicitWhitelistMode(getWhitelistMode());
400 }
401
Bookatzeac923e2019-12-17 12:30:20 -0800402 /**
403 * Whether to treat all packages that are not mentioned at all in the whitelist to be implicitly
404 * whitelisted for the SYSTEM user.
405 */
406 boolean isImplicitWhitelistSystemMode() {
407 return isImplicitWhitelistSystemMode(getWhitelistMode());
408 }
409
Bookatz04d7ae52019-08-05 14:07:12 -0700410 /** See {@link #isEnforceMode()}. */
411 private static boolean isEnforceMode(int whitelistMode) {
412 return (whitelistMode & USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE) != 0;
413 }
414
Bookatz0274a6e2019-11-25 15:31:39 -0800415 /** See {@link #isIgnoreOtaMode()}. */
416 private static boolean isIgnoreOtaMode(int whitelistMode) {
417 return (whitelistMode & USER_TYPE_PACKAGE_WHITELIST_MODE_IGNORE_OTA) != 0;
418 }
419
Bookatz04d7ae52019-08-05 14:07:12 -0700420 /** See {@link #isLogMode()}. */
421 private static boolean isLogMode(int whitelistMode) {
422 return (whitelistMode & USER_TYPE_PACKAGE_WHITELIST_MODE_LOG) != 0;
423 }
424
425 /** See {@link #isImplicitWhitelistMode()}. */
426 private static boolean isImplicitWhitelistMode(int whitelistMode) {
427 return (whitelistMode & USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST) != 0;
428 }
429
Bookatzeac923e2019-12-17 12:30:20 -0800430 /** See {@link #isImplicitWhitelistSystemMode()}. */
431 private static boolean isImplicitWhitelistSystemMode(int whitelistMode) {
432 return (whitelistMode & USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST_SYSTEM) != 0;
433 }
434
Felipe Leme1fbe9b52019-10-21 14:20:08 -0700435 /** Gets the PackageWhitelistMode for use of {@link #mWhitelistedPackagesForUserTypes}. */
Bookatz04d7ae52019-08-05 14:07:12 -0700436 private @PackageWhitelistMode int getWhitelistMode() {
437 final int runtimeMode = SystemProperties.getInt(
438 PACKAGE_WHITELIST_MODE_PROP, USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT);
439 if (runtimeMode != USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT) {
440 return runtimeMode;
441 }
felipeal60aabe62020-04-21 13:20:35 -0700442 return getDeviceDefaultWhitelistMode();
443 }
444
445 /** Gets the PackageWhitelistMode as defined by {@code config_userTypePackageWhitelistMode}. */
446 private @PackageWhitelistMode int getDeviceDefaultWhitelistMode() {
Bookatz04d7ae52019-08-05 14:07:12 -0700447 return Resources.getSystem()
448 .getInteger(com.android.internal.R.integer.config_userTypePackageWhitelistMode);
449 }
450
felipeal60aabe62020-04-21 13:20:35 -0700451 static @NonNull String modeToString(@PackageWhitelistMode int mode) {
452 // Must handle some types separately because they're not bitwise flags
453 switch (mode) {
454 case USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT:
455 return "DEVICE_DEFAULT";
456 case USER_TYPE_PACKAGE_WHITELIST_MODE_NONE:
457 return "NONE";
458 default:
459 return DebugUtils.flagsToString(UserSystemPackageInstaller.class,
460 "USER_TYPE_PACKAGE_WHITELIST_MODE_", mode);
461 }
462 }
463
Bookatz04d7ae52019-08-05 14:07:12 -0700464 /**
465 * Gets the system packages names that should be installed on the given user.
Bookatza8c2d2a2019-11-13 10:10:18 -0800466 * See {@link #getInstallablePackagesForUserType(String)}.
Bookatz04d7ae52019-08-05 14:07:12 -0700467 */
468 private @Nullable Set<String> getInstallablePackagesForUserId(@UserIdInt int userId) {
Bookatza8c2d2a2019-11-13 10:10:18 -0800469 return getInstallablePackagesForUserType(mUm.getUserInfo(userId).userType);
Bookatz04d7ae52019-08-05 14:07:12 -0700470 }
471
472 /**
Bookatza8c2d2a2019-11-13 10:10:18 -0800473 * Gets the system package names that should be installed on users of the given user type, as
Bookatz04d7ae52019-08-05 14:07:12 -0700474 * determined by SystemConfig, the whitelist mode, and the apps actually on the device.
475 * Names are the {@link PackageParser.Package#packageName}, not necessarily the manifest names.
476 *
Bookatza8c2d2a2019-11-13 10:10:18 -0800477 * Returns null if all system packages should be installed (due to enforce-mode being off).
Bookatz04d7ae52019-08-05 14:07:12 -0700478 */
Bookatza8c2d2a2019-11-13 10:10:18 -0800479 @Nullable Set<String> getInstallablePackagesForUserType(String userType) {
Bookatz04d7ae52019-08-05 14:07:12 -0700480 final int mode = getWhitelistMode();
481 if (!isEnforceMode(mode)) {
482 return null;
483 }
Bookatzeac923e2019-12-17 12:30:20 -0800484 final boolean implicitlyWhitelist = isImplicitWhitelistMode(mode)
485 || (isImplicitWhitelistSystemMode(mode) && mUm.isUserTypeSubtypeOfSystem(userType));
Bookatza8c2d2a2019-11-13 10:10:18 -0800486 final Set<String> whitelistedPackages = getWhitelistedPackagesForUserType(userType);
Bookatz04d7ae52019-08-05 14:07:12 -0700487
488 final Set<String> installPackages = new ArraySet<>();
489 final PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
490 pmInt.forEachPackage(pkg -> {
491 if (!pkg.isSystem()) {
492 return;
493 }
Felipe Leme1fbe9b52019-10-21 14:20:08 -0700494 if (shouldInstallPackage(pkg, mWhitelistedPackagesForUserTypes,
Bookatzeac923e2019-12-17 12:30:20 -0800495 whitelistedPackages, implicitlyWhitelist)) {
Bookatz04d7ae52019-08-05 14:07:12 -0700496 // Although the whitelist uses manifest names, this function returns packageNames.
Winson14ff7172019-10-23 10:42:27 -0700497 installPackages.add(pkg.getPackageName());
Bookatz04d7ae52019-08-05 14:07:12 -0700498 }
499 });
500 return installPackages;
501 }
502
503 /**
504 * Returns whether the given system package should be installed on the given user, based on the
505 * the given whitelist of system packages.
506 *
507 * @param sysPkg the system package. Must be a system package; no verification for this is done.
Bookatza8c2d2a2019-11-13 10:10:18 -0800508 * @param userTypeWhitelist map of package manifest names to user types on which they should be
509 * installed. This is only used for overriding the userWhitelist in
510 * certain situations (based on its keyset).
Bookatz04d7ae52019-08-05 14:07:12 -0700511 * @param userWhitelist set of package manifest names that should be installed on this
Bookatzeac923e2019-12-17 12:30:20 -0800512 * <b>particular</b> user. This must be consistent with userTypeWhitelist,
513 * but is passed in separately to avoid repeatedly calculating it from
Bookatz04d7ae52019-08-05 14:07:12 -0700514 * userTypeWhitelist.
Bookatzeac923e2019-12-17 12:30:20 -0800515 * @param implicitlyWhitelist whether non-mentioned packages are implicitly whitelisted.
Bookatz04d7ae52019-08-05 14:07:12 -0700516 */
517 @VisibleForTesting
Winson655a5b92019-10-23 10:49:32 -0700518 static boolean shouldInstallPackage(AndroidPackage sysPkg,
Bookatza8c2d2a2019-11-13 10:10:18 -0800519 @NonNull ArrayMap<String, Long> userTypeWhitelist,
Bookatzeac923e2019-12-17 12:30:20 -0800520 @NonNull Set<String> userWhitelist, boolean implicitlyWhitelist) {
Winson14ff7172019-10-23 10:42:27 -0700521 final String pkgName = sysPkg.getManifestPackageName();
Bookatzeac923e2019-12-17 12:30:20 -0800522 return (implicitlyWhitelist && !userTypeWhitelist.containsKey(pkgName))
Bookatz04d7ae52019-08-05 14:07:12 -0700523 || userWhitelist.contains(pkgName);
Bookatz04d7ae52019-08-05 14:07:12 -0700524 }
525
526 /**
Bookatza8c2d2a2019-11-13 10:10:18 -0800527 * Gets the package manifest names that are whitelisted for users of the given user type,
Bookatz04d7ae52019-08-05 14:07:12 -0700528 * as determined by SystemConfig.
529 */
530 @VisibleForTesting
Bookatza8c2d2a2019-11-13 10:10:18 -0800531 @NonNull Set<String> getWhitelistedPackagesForUserType(String userType) {
532 final long userTypeMask = getUserTypeMask(userType);
533 final Set<String> installablePkgs = new ArraySet<>(mWhitelistedPackagesForUserTypes.size());
Felipe Leme1fbe9b52019-10-21 14:20:08 -0700534 for (int i = 0; i < mWhitelistedPackagesForUserTypes.size(); i++) {
Bookatza8c2d2a2019-11-13 10:10:18 -0800535 final String pkgName = mWhitelistedPackagesForUserTypes.keyAt(i);
536 final long whitelistedUserTypes = mWhitelistedPackagesForUserTypes.valueAt(i);
537 if ((userTypeMask & whitelistedUserTypes) != 0) {
Bookatz04d7ae52019-08-05 14:07:12 -0700538 installablePkgs.add(pkgName);
539 }
540 }
541 return installablePkgs;
542 }
543
544 /**
545 * Set of package manifest names that are included anywhere in the package-to-user-type
546 * whitelist, as determined by SystemConfig.
547 *
548 * Packages that are whitelisted, but then blacklisted so that they aren't to be installed on
549 * any user, are still present in this list, since that is a valid scenario (e.g. if an OEM
550 * completely blacklists an AOSP app).
551 */
552 private Set<String> getWhitelistedSystemPackages() {
Felipe Leme1fbe9b52019-10-21 14:20:08 -0700553 return mWhitelistedPackagesForUserTypes.keySet();
Bookatz04d7ae52019-08-05 14:07:12 -0700554 }
555
556 /**
Bookatza8c2d2a2019-11-13 10:10:18 -0800557 * Returns a map of package manifest names to the bit set representing (via
558 * {@link #getUserTypeMask}) the user types on which they are to be installed.
Bookatz04d7ae52019-08-05 14:07:12 -0700559 * Also, clears this data from SystemConfig where it was stored inefficiently (and therefore
560 * should be called exactly once, even if the data isn't useful).
561 *
562 * Any system packages not present in this map should not even be on the device at all.
563 * To enforce this:
564 * <ul>
565 * <li>Illegal user types are ignored.</li>
566 * <li>Packages that never whitelisted at all (even if they are explicitly blacklisted) are
567 * ignored.</li>
568 * <li>Packages that are blacklisted whenever they are whitelisted will be stored with the
Bookatza8c2d2a2019-11-13 10:10:18 -0800569 * value 0 (since this is a valid scenario, e.g. if an OEM completely blacklists an
570 * AOSP app).</li>
Bookatz04d7ae52019-08-05 14:07:12 -0700571 * </ul>
Bookatza8c2d2a2019-11-13 10:10:18 -0800572 *
573 * @see #mWhitelistedPackagesForUserTypes
Bookatz04d7ae52019-08-05 14:07:12 -0700574 */
575 @VisibleForTesting
Bookatza8c2d2a2019-11-13 10:10:18 -0800576 ArrayMap<String, Long> determineWhitelistedPackagesForUserTypes(SystemConfig sysConfig) {
577 // We first get the list of user types that correspond to FULL, SYSTEM, and PROFILE.
578 final Map<String, Long> baseTypeBitSets = getBaseTypeBitSets();
Bookatz04d7ae52019-08-05 14:07:12 -0700579
580 final ArrayMap<String, Set<String>> whitelist =
581 sysConfig.getAndClearPackageToUserTypeWhitelist();
582 // result maps packageName -> userTypes on which the package should be installed.
Bookatza8c2d2a2019-11-13 10:10:18 -0800583 final ArrayMap<String, Long> result = new ArrayMap<>(whitelist.size() + 1);
Bookatz04d7ae52019-08-05 14:07:12 -0700584 // First, do the whitelisted user types.
585 for (int i = 0; i < whitelist.size(); i++) {
Bookatz12c292e2019-11-14 11:00:13 -0800586 final String pkgName = whitelist.keyAt(i).intern();
Bookatza8c2d2a2019-11-13 10:10:18 -0800587 final long typesBitSet = getTypesBitSet(whitelist.valueAt(i), baseTypeBitSets);
588 if (typesBitSet != 0) {
589 result.put(pkgName, typesBitSet);
Bookatz04d7ae52019-08-05 14:07:12 -0700590 }
591 }
592 // Then, un-whitelist any blacklisted user types.
Bookatz04d7ae52019-08-05 14:07:12 -0700593 final ArrayMap<String, Set<String>> blacklist =
594 sysConfig.getAndClearPackageToUserTypeBlacklist();
595 for (int i = 0; i < blacklist.size(); i++) {
Bookatz12c292e2019-11-14 11:00:13 -0800596 final String pkgName = blacklist.keyAt(i).intern();
Bookatza8c2d2a2019-11-13 10:10:18 -0800597 final long nonTypesBitSet = getTypesBitSet(blacklist.valueAt(i), baseTypeBitSets);
598 final Long typesBitSet = result.get(pkgName);
599 if (typesBitSet != null) {
600 result.put(pkgName, typesBitSet & ~nonTypesBitSet);
601 } else if (nonTypesBitSet != 0) {
602 // Package was never whitelisted but is validly blacklisted.
603 result.put(pkgName, 0L);
Bookatz04d7ae52019-08-05 14:07:12 -0700604 }
605 }
606 // Regardless of the whitelists/blacklists, ensure mandatory packages.
Bookatza8c2d2a2019-11-13 10:10:18 -0800607 result.put("android", ~0L);
Bookatz04d7ae52019-08-05 14:07:12 -0700608 return result;
609 }
610
Bookatza8c2d2a2019-11-13 10:10:18 -0800611 /**
612 * Returns the bitmask (with exactly one 1) corresponding to the given userType.
613 * Returns 0 if no such userType exists.
614 */
615 @VisibleForTesting
616 long getUserTypeMask(String userType) {
617 final int userTypeIndex = Arrays.binarySearch(mUserTypes, userType);
618 final long userTypeMask = userTypeIndex >= 0 ? (1 << userTypeIndex) : 0;
619 return userTypeMask;
620 }
621
622 /**
623 * Returns the mapping from the name of each base type to the bitset (as defined by
624 * {@link #getUserTypeMask}) of user types to which it corresponds (i.e. the base's subtypes).
625 * <p>
626 * E.g. if "android.type.ex" is a FULL user type for which getUserTypeMask() returns (1 << 3),
627 * then getBaseTypeBitSets().get("FULL") will contain true (1) in position 3.
628 */
629 private Map<String, Long> getBaseTypeBitSets() {
630 long typesBitSetFull = 0;
631 long typesBitSetSystem = 0;
632 long typesBitSetProfile = 0;
633 for (int idx = 0; idx < mUserTypes.length; idx++) {
634 if (mUm.isUserTypeSubtypeOfFull(mUserTypes[idx])) {
635 typesBitSetFull |= (1 << idx);
636 }
637 if (mUm.isUserTypeSubtypeOfSystem(mUserTypes[idx])) {
638 typesBitSetSystem |= (1 << idx);
639 }
640 if (mUm.isUserTypeSubtypeOfProfile(mUserTypes[idx])) {
641 typesBitSetProfile |= (1 << idx);
Bookatz04d7ae52019-08-05 14:07:12 -0700642 }
643 }
Bookatza8c2d2a2019-11-13 10:10:18 -0800644
645 Map<String, Long> result = new ArrayMap<>(3);
646 result.put("FULL", typesBitSetFull);
647 result.put("SYSTEM", typesBitSetSystem);
648 result.put("PROFILE", typesBitSetProfile);
649 return result;
650 }
651
652 /**
653 * Converts a list of user types and base types, as used in SystemConfig, to a bit set
654 * representing (via {@link #getUserTypeMask}) user types.
655 *
656 * Returns 0 if userTypes does not contain any valid user or base types.
657 *
658 * @param baseTypeBitSets a map from the base types (FULL/SYSTEM/PROFILE) to their subtypes
659 * (represented as a bitset, as defined by {@link #getUserTypeMask}).
660 * (This can be created by {@link #getBaseTypeBitSets}.)
661 */
662 private long getTypesBitSet(Iterable<String> userTypes, Map<String, Long> baseTypeBitSets) {
663 long resultBitSet = 0;
664 for (String type : userTypes) {
665 // See if userType is a base type, like FULL.
666 final Long baseTypeBitSet = baseTypeBitSets.get(type);
667 if (baseTypeBitSet != null) {
668 resultBitSet |= baseTypeBitSet;
669 continue;
670 }
671 // userType wasn't a base type, so it should be the name of a specific user type.
672 final long userTypeBitSet = getUserTypeMask(type);
673 if (userTypeBitSet != 0) {
674 resultBitSet |= userTypeBitSet;
675 continue;
676 }
677 Slog.w(TAG, "SystemConfig contained an invalid user type: " + type);
678 }
679 return resultBitSet;
680 }
681
682 /** Returns a sorted array consisting of the keyset of the provided map. */
683 private static String[] getAndSortKeysFromMap(ArrayMap<String, ?> map) {
684 final String[] userTypeList = new String[map.size()];
685 for (int i = 0; i < map.size(); i++) {
686 userTypeList[i] = map.keyAt(i);
687 }
688 Arrays.sort(userTypeList);
689 return userTypeList;
Bookatz04d7ae52019-08-05 14:07:12 -0700690 }
691
692 void dump(PrintWriter pw) {
felipeala2f45ef2020-04-15 08:39:36 -0700693 try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ")) {
694 dumpIndented(ipw);
695 }
696 }
697
698 private void dumpIndented(IndentingPrintWriter pw) {
Felipe Leme1fbe9b52019-10-21 14:20:08 -0700699 final int mode = getWhitelistMode();
700 pw.println("Whitelisted packages per user type");
felipeala2f45ef2020-04-15 08:39:36 -0700701
702 pw.increaseIndent();
703 pw.print("Mode: ");
Felipe Leme1fbe9b52019-10-21 14:20:08 -0700704 pw.print(mode);
705 pw.print(isEnforceMode(mode) ? " (enforced)" : "");
706 pw.print(isLogMode(mode) ? " (logged)" : "");
707 pw.print(isImplicitWhitelistMode(mode) ? " (implicit)" : "");
Bookatz0274a6e2019-11-25 15:31:39 -0800708 pw.print(isIgnoreOtaMode(mode) ? " (ignore OTAs)" : "");
Felipe Leme1fbe9b52019-10-21 14:20:08 -0700709 pw.println();
felipeala2f45ef2020-04-15 08:39:36 -0700710 pw.decreaseIndent();
Felipe Leme1fbe9b52019-10-21 14:20:08 -0700711
felipeala2f45ef2020-04-15 08:39:36 -0700712 pw.increaseIndent();
713 pw.println("Legend");
714 pw.increaseIndent();
Bookatza8c2d2a2019-11-13 10:10:18 -0800715 for (int idx = 0; idx < mUserTypes.length; idx++) {
felipeala2f45ef2020-04-15 08:39:36 -0700716 pw.println(idx + " -> " + mUserTypes[idx]);
Bookatza8c2d2a2019-11-13 10:10:18 -0800717 }
felipeala2f45ef2020-04-15 08:39:36 -0700718 pw.decreaseIndent(); pw.decreaseIndent();
Bookatza8c2d2a2019-11-13 10:10:18 -0800719
felipeala2f45ef2020-04-15 08:39:36 -0700720 pw.increaseIndent();
Felipe Leme1fbe9b52019-10-21 14:20:08 -0700721 final int size = mWhitelistedPackagesForUserTypes.size();
Felipe Leme2b8c51b2019-10-09 17:21:33 -0700722 if (size == 0) {
felipeala2f45ef2020-04-15 08:39:36 -0700723 pw.println("No packages");
724 pw.decreaseIndent();
Felipe Leme2b8c51b2019-10-09 17:21:33 -0700725 return;
726 }
felipeala2f45ef2020-04-15 08:39:36 -0700727 pw.print(size); pw.println(" packages:");
728 pw.increaseIndent();
Bookatza8c2d2a2019-11-13 10:10:18 -0800729 for (int pkgIdx = 0; pkgIdx < size; pkgIdx++) {
730 final String pkgName = mWhitelistedPackagesForUserTypes.keyAt(pkgIdx);
felipeala2f45ef2020-04-15 08:39:36 -0700731 pw.print(pkgName); pw.print(": ");
Bookatza8c2d2a2019-11-13 10:10:18 -0800732 final long userTypesBitSet = mWhitelistedPackagesForUserTypes.valueAt(pkgIdx);
733 for (int idx = 0; idx < mUserTypes.length; idx++) {
734 if ((userTypesBitSet & (1 << idx)) != 0) {
735 pw.print(idx); pw.print(" ");
736 }
737 }
738 pw.println();
Bookatz04d7ae52019-08-05 14:07:12 -0700739 }
felipeala2f45ef2020-04-15 08:39:36 -0700740 pw.decreaseIndent(); pw.decreaseIndent();
741
742 pw.increaseIndent();
felipeal60aabe62020-04-21 13:20:35 -0700743 dumpPackageWhitelistProblems(pw, mode, /* verbose= */ true, /* criticalOnly= */ false);
felipeala2f45ef2020-04-15 08:39:36 -0700744 pw.decreaseIndent();
745 }
746
felipeal60aabe62020-04-21 13:20:35 -0700747 void dumpPackageWhitelistProblems(IndentingPrintWriter pw, @PackageWhitelistMode int mode,
748 boolean verbose, boolean criticalOnly) {
749 // Handle special cases first
750 if (mode == USER_TYPE_PACKAGE_WHITELIST_MODE_NONE) {
751 mode = getWhitelistMode();
752 } else if (mode == USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT) {
753 mode = getDeviceDefaultWhitelistMode();
754 }
felipeala1c0dab2020-05-12 17:32:23 -0700755 if (criticalOnly) {
756 // Flip-out log mode
757 mode &= ~USER_TYPE_PACKAGE_WHITELIST_MODE_LOG;
758 }
felipeal60aabe62020-04-21 13:20:35 -0700759 Slog.v(TAG, "dumpPackageWhitelistProblems(): using mode " + modeToString(mode));
felipeala2f45ef2020-04-15 08:39:36 -0700760
felipeal60aabe62020-04-21 13:20:35 -0700761 final List<String> errors = getPackagesWhitelistErrors(mode);
762 showIssues(pw, verbose, errors, "errors");
felipeala2f45ef2020-04-15 08:39:36 -0700763
felipeal60aabe62020-04-21 13:20:35 -0700764 if (criticalOnly) return;
765
766 final List<String> warnings = getPackagesWhitelistWarnings();
767 showIssues(pw, verbose, warnings, "warnings");
768 }
769
770 private static void showIssues(IndentingPrintWriter pw, boolean verbose, List<String> issues,
771 String issueType) {
772 final int size = issues.size();
felipeala2f45ef2020-04-15 08:39:36 -0700773 if (size == 0) {
774 if (verbose) {
felipeal60aabe62020-04-21 13:20:35 -0700775 pw.print("No "); pw.println(issueType);
felipeala2f45ef2020-04-15 08:39:36 -0700776 }
777 return;
778 }
felipeala2f45ef2020-04-15 08:39:36 -0700779 if (verbose) {
felipeal60aabe62020-04-21 13:20:35 -0700780 pw.print(size); pw.print(' '); pw.println(issueType);
felipeala2f45ef2020-04-15 08:39:36 -0700781 pw.increaseIndent();
782 }
783 for (int i = 0; i < size; i++) {
felipeal60aabe62020-04-21 13:20:35 -0700784 pw.println(issues.get(i));
felipeala2f45ef2020-04-15 08:39:36 -0700785 }
786 if (verbose) {
787 pw.decreaseIndent();
788 }
Bookatz04d7ae52019-08-05 14:07:12 -0700789 }
790}