blob: 7f73baefcabd747da32f867bbb14bf860a9e7346 [file] [log] [blame]
Patrick Baumann6c1c8092019-06-27 14:55:44 -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
Patrick Baumann88318e02019-11-04 14:22:25 -080019import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
Patrick Baumanndfb121d2019-08-05 14:36:19 -070020import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
21
Patrick Baumann6c1c8092019-06-27 14:55:44 -070022import android.Manifest;
Patrick Baumann051d75c2020-01-10 15:13:48 -080023import android.annotation.NonNull;
Patrick Baumann6c1c8092019-06-27 14:55:44 -070024import android.annotation.Nullable;
Patrick Baumann6c1c8092019-06-27 14:55:44 -070025import android.content.Intent;
Patrick Baumann0c9257b2019-08-20 12:50:37 -070026import android.content.IntentFilter;
Patrick Baumann6c1c8092019-06-27 14:55:44 -070027import android.content.pm.PackageManager;
Patrick Baumann051d75c2020-01-10 15:13:48 -080028import android.content.pm.PackageParser;
Winson14ff7172019-10-23 10:42:27 -070029import android.content.pm.parsing.AndroidPackage;
30import android.content.pm.parsing.ComponentParseUtils;
Winson655a5b92019-10-23 10:49:32 -070031import android.content.pm.parsing.ComponentParseUtils.ParsedActivity;
Winson14ff7172019-10-23 10:42:27 -070032import android.content.pm.parsing.ComponentParseUtils.ParsedComponent;
33import android.content.pm.parsing.ComponentParseUtils.ParsedIntentInfo;
34import android.content.pm.parsing.ComponentParseUtils.ParsedProvider;
Winson655a5b92019-10-23 10:49:32 -070035import android.content.pm.parsing.ComponentParseUtils.ParsedService;
Patrick Baumann0c9257b2019-08-20 12:50:37 -070036import android.net.Uri;
Patrick Baumann6c1c8092019-06-27 14:55:44 -070037import android.os.Process;
Patrick Baumann88318e02019-11-04 14:22:25 -080038import android.os.Trace;
Patrick Baumannb9a49f42019-11-13 16:42:18 -080039import android.os.UserHandle;
Patrick Baumanndfb121d2019-08-05 14:36:19 -070040import android.provider.DeviceConfig;
Patrick Baumannb9a49f42019-11-13 16:42:18 -080041import android.text.TextUtils;
42import android.util.ArrayMap;
Patrick Baumann6c1c8092019-06-27 14:55:44 -070043import android.util.ArraySet;
44import android.util.Slog;
45import android.util.SparseArray;
Patrick Baumannb9a49f42019-11-13 16:42:18 -080046import android.util.SparseSetArray;
Patrick Baumann6c1c8092019-06-27 14:55:44 -070047
48import com.android.internal.R;
Patrick Baumanna94cbc12019-09-17 08:08:10 -070049import com.android.internal.annotations.VisibleForTesting;
Patrick Baumannb9a49f42019-11-13 16:42:18 -080050import com.android.internal.util.ArrayUtils;
Patrick Baumanndfb121d2019-08-05 14:36:19 -070051import com.android.server.FgThread;
Winson Chiu2fdaf812019-12-13 20:01:15 +000052import com.android.server.om.OverlayReferenceMapper;
Patrick Baumann6c1c8092019-06-27 14:55:44 -070053
Patrick Baumanna7ad66d2019-08-12 11:11:08 -070054import java.io.PrintWriter;
Winson655a5b92019-10-23 10:49:32 -070055import java.util.List;
Patrick Baumanna7ad66d2019-08-12 11:11:08 -070056import java.util.Objects;
Patrick Baumann6c1c8092019-06-27 14:55:44 -070057import java.util.Set;
Patrick Baumanne2d9d712020-01-10 13:32:09 -080058import java.util.StringTokenizer;
Patrick Baumann6c1c8092019-06-27 14:55:44 -070059
60/**
61 * The entity responsible for filtering visibility between apps based on declarations in their
62 * manifests.
63 */
Patrick Baumanna94cbc12019-09-17 08:08:10 -070064@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
65public class AppsFilter {
Patrick Baumann6c1c8092019-06-27 14:55:44 -070066
Patrick Baumannb9a49f42019-11-13 16:42:18 -080067 private static final String TAG = "AppsFilter";
Patrick Baumannaf9c3fe2019-08-12 08:13:32 -070068
Patrick Baumann40ed99a2019-08-12 09:43:10 -070069 // Logs all filtering instead of enforcing
70 private static final boolean DEBUG_ALLOW_ALL = false;
Patrick Baumann57556b62020-01-29 07:23:39 -080071 private static final boolean DEBUG_LOGGING = false;
72 private static final boolean FEATURE_ENABLED_BY_DEFAULT = false;
Patrick Baumann40ed99a2019-08-12 09:43:10 -070073
Patrick Baumann6c1c8092019-06-27 14:55:44 -070074 /**
Patrick Baumannb9a49f42019-11-13 16:42:18 -080075 * This contains a list of app UIDs that are implicitly queryable because another app explicitly
Patrick Baumann6c1c8092019-06-27 14:55:44 -070076 * interacted with it. For example, if application A starts a service in application B,
77 * application B is implicitly allowed to query for application A; regardless of any manifest
78 * entries.
79 */
Patrick Baumannb9a49f42019-11-13 16:42:18 -080080 private final SparseSetArray<Integer> mImplicitlyQueryable = new SparseSetArray<>();
Patrick Baumann6c1c8092019-06-27 14:55:44 -070081
82 /**
Patrick Baumannb9a49f42019-11-13 16:42:18 -080083 * A mapping from the set of App IDs that query other App IDs via package name to the
Patrick Baumann6c1c8092019-06-27 14:55:44 -070084 * list of packages that they can see.
85 */
Patrick Baumannb9a49f42019-11-13 16:42:18 -080086 private final SparseSetArray<Integer> mQueriesViaPackage = new SparseSetArray<>();
Patrick Baumann6c1c8092019-06-27 14:55:44 -070087
88 /**
Patrick Baumannb9a49f42019-11-13 16:42:18 -080089 * A mapping from the set of App IDs that query others via intent to the list
Patrick Baumann6c1c8092019-06-27 14:55:44 -070090 * of packages that the intents resolve to.
91 */
Patrick Baumannb9a49f42019-11-13 16:42:18 -080092 private final SparseSetArray<Integer> mQueriesViaIntent = new SparseSetArray<>();
Patrick Baumann6c1c8092019-06-27 14:55:44 -070093
94 /**
Patrick Baumannb9a49f42019-11-13 16:42:18 -080095 * A set of App IDs that are always queryable by any package, regardless of their manifest
Patrick Baumann6c1c8092019-06-27 14:55:44 -070096 * content.
97 */
Patrick Baumannb9a49f42019-11-13 16:42:18 -080098 private final ArraySet<Integer> mForceQueryable = new ArraySet<>();
99
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700100 /**
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800101 * The set of package names provided by the device that should be force queryable regardless of
102 * their manifest contents.
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700103 */
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800104 private final String[] mForceQueryableByDevicePackageNames;
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700105
106 /** True if all system apps should be made queryable by default. */
107 private final boolean mSystemAppsQueryable;
108
Patrick Baumanndfb121d2019-08-05 14:36:19 -0700109 private final FeatureConfig mFeatureConfig;
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700110
Winson Chiu2fdaf812019-12-13 20:01:15 +0000111 private final OverlayReferenceMapper mOverlayReferenceMapper;
Patrick Baumann051d75c2020-01-10 15:13:48 -0800112 private PackageParser.SigningDetails mSystemSigningDetails;
Winson Chiu2fdaf812019-12-13 20:01:15 +0000113
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800114 AppsFilter(FeatureConfig featureConfig, String[] forceQueryableWhitelist,
Winson Chiu2fdaf812019-12-13 20:01:15 +0000115 boolean systemAppsQueryable,
116 @Nullable OverlayReferenceMapper.Provider overlayProvider) {
Patrick Baumanndfb121d2019-08-05 14:36:19 -0700117 mFeatureConfig = featureConfig;
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800118 mForceQueryableByDevicePackageNames = forceQueryableWhitelist;
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700119 mSystemAppsQueryable = systemAppsQueryable;
Winson Chiu2fdaf812019-12-13 20:01:15 +0000120 mOverlayReferenceMapper = new OverlayReferenceMapper(true /*deferRebuild*/,
121 overlayProvider);
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700122 }
123
Patrick Baumanndfb121d2019-08-05 14:36:19 -0700124 public interface FeatureConfig {
125 /** Called when the system is ready and components can be queried. */
126 void onSystemReady();
Patrick Baumannaf9c3fe2019-08-12 08:13:32 -0700127
Patrick Baumanndfb121d2019-08-05 14:36:19 -0700128 /** @return true if we should filter apps at all. */
129 boolean isGloballyEnabled();
130
131 /** @return true if the feature is enabled for the given package. */
Winson655a5b92019-10-23 10:49:32 -0700132 boolean packageIsEnabled(AndroidPackage pkg);
Patrick Baumanndfb121d2019-08-05 14:36:19 -0700133 }
134
135 private static class FeatureConfigImpl implements FeatureConfig {
136 private static final String FILTERING_ENABLED_NAME = "package_query_filtering_enabled";
Anna Trostanetski66bb1662019-10-29 16:44:25 +0000137 private final PackageManagerService.Injector mInjector;
Patrick Baumann57556b62020-01-29 07:23:39 -0800138 private volatile boolean mFeatureEnabled = FEATURE_ENABLED_BY_DEFAULT;
Patrick Baumanndfb121d2019-08-05 14:36:19 -0700139
140 private FeatureConfigImpl(PackageManagerService.Injector injector) {
Anna Trostanetski66bb1662019-10-29 16:44:25 +0000141 mInjector = injector;
Patrick Baumanndfb121d2019-08-05 14:36:19 -0700142 }
143
144 @Override
145 public void onSystemReady() {
146 mFeatureEnabled = DeviceConfig.getBoolean(
Patrick Baumann57556b62020-01-29 07:23:39 -0800147 NAMESPACE_PACKAGE_MANAGER_SERVICE, FILTERING_ENABLED_NAME,
148 FEATURE_ENABLED_BY_DEFAULT);
Patrick Baumanndfb121d2019-08-05 14:36:19 -0700149 DeviceConfig.addOnPropertiesChangedListener(
150 NAMESPACE_PACKAGE_MANAGER_SERVICE, FgThread.getExecutor(),
151 properties -> {
Linus Tufvesson87776d62019-12-05 16:15:10 +0000152 if (properties.getKeyset().contains(FILTERING_ENABLED_NAME)) {
153 synchronized (FeatureConfigImpl.this) {
154 mFeatureEnabled = properties.getBoolean(FILTERING_ENABLED_NAME,
Patrick Baumann57556b62020-01-29 07:23:39 -0800155 FEATURE_ENABLED_BY_DEFAULT);
Linus Tufvesson87776d62019-12-05 16:15:10 +0000156 }
Patrick Baumanndfb121d2019-08-05 14:36:19 -0700157 }
158 });
159 }
160
161 @Override
162 public boolean isGloballyEnabled() {
Patrick Baumann88318e02019-11-04 14:22:25 -0800163 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "isGloballyEnabled");
164 try {
165 return mFeatureEnabled;
166 } finally {
167 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
168 }
Patrick Baumanndfb121d2019-08-05 14:36:19 -0700169 }
170
171 @Override
Winson655a5b92019-10-23 10:49:32 -0700172 public boolean packageIsEnabled(AndroidPackage pkg) {
Patrick Baumann88318e02019-11-04 14:22:25 -0800173 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "packageIsEnabled");
174 try {
Winson655a5b92019-10-23 10:49:32 -0700175 // TODO(b/135203078): Do not use toAppInfo
Patrick Baumann88318e02019-11-04 14:22:25 -0800176 return mInjector.getCompatibility().isChangeEnabled(
Winson6571c8a2019-10-23 17:00:42 -0700177 PackageManager.FILTER_APPLICATION_QUERY, pkg.toAppInfoWithoutState());
Patrick Baumann88318e02019-11-04 14:22:25 -0800178 } finally {
179 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
180 }
Patrick Baumanndfb121d2019-08-05 14:36:19 -0700181 }
182 }
183
Patrick Baumanndfb121d2019-08-05 14:36:19 -0700184 public static AppsFilter create(PackageManagerService.Injector injector) {
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700185 final boolean forceSystemAppsQueryable =
Patrick Baumanndfb121d2019-08-05 14:36:19 -0700186 injector.getContext().getResources()
187 .getBoolean(R.bool.config_forceSystemPackagesQueryable);
188 final FeatureConfig featureConfig = new FeatureConfigImpl(injector);
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700189 final String[] forcedQueryablePackageNames;
190 if (forceSystemAppsQueryable) {
191 // all system apps already queryable, no need to read and parse individual exceptions
192 forcedQueryablePackageNames = new String[]{};
193 } else {
194 forcedQueryablePackageNames =
Patrick Baumanndfb121d2019-08-05 14:36:19 -0700195 injector.getContext().getResources()
196 .getStringArray(R.array.config_forceQueryablePackages);
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700197 for (int i = 0; i < forcedQueryablePackageNames.length; i++) {
198 forcedQueryablePackageNames[i] = forcedQueryablePackageNames[i].intern();
199 }
200 }
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800201 return new AppsFilter(featureConfig, forcedQueryablePackageNames,
Winson Chiu2fdaf812019-12-13 20:01:15 +0000202 forceSystemAppsQueryable, null);
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700203 }
204
205 /** Returns true if the querying package may query for the potential target package */
Winson655a5b92019-10-23 10:49:32 -0700206 private static boolean canQueryViaIntent(AndroidPackage querying,
207 AndroidPackage potentialTarget) {
208 if (querying.getQueriesIntents() == null) {
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700209 return false;
210 }
Winson655a5b92019-10-23 10:49:32 -0700211 for (Intent intent : querying.getQueriesIntents()) {
Patrick Baumann58632332019-10-09 10:12:10 -0700212 if (matches(intent, potentialTarget)) {
Patrick Baumann0c9257b2019-08-20 12:50:37 -0700213 return true;
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700214 }
215 }
216 return false;
217 }
218
Alan Stokesa547c5c62020-01-20 11:36:08 +0000219 private static boolean canQueryViaPackage(AndroidPackage querying,
220 AndroidPackage potentialTarget) {
221 return querying.getQueriesPackages() != null
222 && querying.getQueriesPackages().contains(potentialTarget.getPackageName());
223 }
224
225 private static boolean canQueryAsInstaller(PackageSetting querying,
226 AndroidPackage potentialTarget) {
227 final InstallSource installSource = querying.installSource;
228 if (potentialTarget.getPackageName().equals(installSource.installerPackageName)) {
229 return true;
230 }
231 if (!installSource.isInitiatingPackageUninstalled
232 && potentialTarget.getPackageName().equals(installSource.initiatingPackageName)) {
233 return true;
234 }
235 return false;
236 }
237
Winson655a5b92019-10-23 10:49:32 -0700238 private static boolean matches(Intent intent, AndroidPackage potentialTarget) {
239 for (int p = ArrayUtils.size(potentialTarget.getProviders()) - 1; p >= 0; p--) {
240 ParsedProvider provider = potentialTarget.getProviders().get(p);
241 if (!provider.isExported()) {
Patrick Baumann58632332019-10-09 10:12:10 -0700242 continue;
243 }
Patrick Baumann0c9257b2019-08-20 12:50:37 -0700244 final Uri data = intent.getData();
Patrick Baumanne2d9d712020-01-10 13:32:09 -0800245 if (!"content".equalsIgnoreCase(intent.getScheme()) || data == null
246 || provider.getAuthority() == null) {
247 continue;
248 }
249 StringTokenizer authorities = new StringTokenizer(provider.getAuthority(), ";", false);
250 while (authorities.hasMoreElements()) {
251 if (Objects.equals(authorities.nextElement(), data.getAuthority())) {
252 return true;
253 }
Patrick Baumann0c9257b2019-08-20 12:50:37 -0700254 }
255 }
Winson655a5b92019-10-23 10:49:32 -0700256 for (int s = ArrayUtils.size(potentialTarget.getServices()) - 1; s >= 0; s--) {
257 ParsedService service = potentialTarget.getServices().get(s);
258 if (!service.exported) {
Patrick Baumann58632332019-10-09 10:12:10 -0700259 continue;
260 }
261 if (matchesAnyFilter(intent, service)) {
262 return true;
263 }
264 }
Winson655a5b92019-10-23 10:49:32 -0700265 for (int a = ArrayUtils.size(potentialTarget.getActivities()) - 1; a >= 0; a--) {
266 ParsedActivity activity = potentialTarget.getActivities().get(a);
267 if (!activity.exported) {
Patrick Baumann58632332019-10-09 10:12:10 -0700268 continue;
269 }
270 if (matchesAnyFilter(intent, activity)) {
271 return true;
272 }
273 }
Winson655a5b92019-10-23 10:49:32 -0700274 for (int r = ArrayUtils.size(potentialTarget.getReceivers()) - 1; r >= 0; r--) {
275 ParsedActivity receiver = potentialTarget.getReceivers().get(r);
276 if (!receiver.exported) {
Patrick Baumann58632332019-10-09 10:12:10 -0700277 continue;
278 }
279 if (matchesAnyFilter(intent, receiver)) {
280 return true;
281 }
282 }
283 return false;
284 }
Patrick Baumann0c9257b2019-08-20 12:50:37 -0700285
Patrick Baumann58632332019-10-09 10:12:10 -0700286 private static boolean matchesAnyFilter(
Winson655a5b92019-10-23 10:49:32 -0700287 Intent intent, ParsedComponent<? extends ParsedIntentInfo> component) {
288 List<? extends ParsedIntentInfo> intents = component.intents;
289 for (int i = ArrayUtils.size(intents) - 1; i >= 0; i--) {
Patrick Baumann58632332019-10-09 10:12:10 -0700290 IntentFilter intentFilter = intents.get(i);
291 if (intentFilter.match(intent.getAction(), intent.getType(), intent.getScheme(),
292 intent.getData(), intent.getCategories(), "AppsFilter") > 0) {
293 return true;
Patrick Baumann0c9257b2019-08-20 12:50:37 -0700294 }
295 }
296 return false;
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700297 }
298
299 /**
Patrick Baumannde37e432019-08-28 09:51:29 -0700300 * Grants access based on an interaction between a calling and target package, granting
301 * visibility of the caller from the target.
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700302 *
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800303 * @param callingUid the uid initiating the interaction
304 * @param targetUid the uid being interacted with and thus gaining visibility of the
305 * initiating uid.
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700306 */
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800307 public void grantImplicitAccess(int callingUid, int targetUid) {
Patrick Baumanndcf3d372020-01-16 15:22:22 -0800308 if (targetUid != callingUid
309 && mImplicitlyQueryable.add(targetUid, callingUid) && DEBUG_LOGGING) {
310 Slog.wtf(TAG, "implicit access granted: " + targetUid + " -> " + callingUid);
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700311 }
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700312 }
313
Patrick Baumanndfb121d2019-08-05 14:36:19 -0700314 public void onSystemReady() {
315 mFeatureConfig.onSystemReady();
Winson Chiu2fdaf812019-12-13 20:01:15 +0000316 mOverlayReferenceMapper.rebuildIfDeferred();
Patrick Baumanndfb121d2019-08-05 14:36:19 -0700317 }
318
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700319 /**
320 * Adds a package that should be considered when filtering visibility between apps.
321 *
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800322 * @param newPkgSetting the new setting being added
323 * @param existingSettings all other settings currently on the device.
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700324 */
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800325 public void addPackage(PackageSetting newPkgSetting,
326 ArrayMap<String, PackageSetting> existingSettings) {
Patrick Baumann051d75c2020-01-10 15:13:48 -0800327 if (Objects.equals("android", newPkgSetting.name)) {
328 // let's set aside the framework signatures
329 mSystemSigningDetails = newPkgSetting.signatures.mSigningDetails;
330 // and since we add overlays before we add the framework, let's revisit already added
331 // packages for signature matches
332 for (PackageSetting setting : existingSettings.values()) {
333 if (isSystemSigned(mSystemSigningDetails, setting)) {
334 mForceQueryable.add(setting.appId);
335 }
336 }
337 }
Patrick Baumann88318e02019-11-04 14:22:25 -0800338 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "filter.addPackage");
339 try {
Winson655a5b92019-10-23 10:49:32 -0700340 final AndroidPackage newPkg = newPkgSetting.pkg;
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800341 if (newPkg == null) {
342 // nothing to add
343 return;
344 }
345
346 final boolean newIsForceQueryable =
347 mForceQueryable.contains(newPkgSetting.appId)
348 /* shared user that is already force queryable */
Winson655a5b92019-10-23 10:49:32 -0700349 || newPkg.isForceQueryable()
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800350 || (newPkgSetting.isSystem() && (mSystemAppsQueryable
351 || ArrayUtils.contains(mForceQueryableByDevicePackageNames,
Winson655a5b92019-10-23 10:49:32 -0700352 newPkg.getPackageName())));
Patrick Baumann051d75c2020-01-10 15:13:48 -0800353 if (newIsForceQueryable
354 || (mSystemSigningDetails != null
355 && isSystemSigned(mSystemSigningDetails, newPkgSetting))) {
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800356 mForceQueryable.add(newPkgSetting.appId);
357 }
358
359 for (int i = existingSettings.size() - 1; i >= 0; i--) {
360 final PackageSetting existingSetting = existingSettings.valueAt(i);
361 if (existingSetting.appId == newPkgSetting.appId || existingSetting.pkg == null) {
362 continue;
363 }
Winson655a5b92019-10-23 10:49:32 -0700364 final AndroidPackage existingPkg = existingSetting.pkg;
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800365 // let's evaluate the ability of already added packages to see this new package
366 if (!newIsForceQueryable) {
367 if (canQueryViaIntent(existingPkg, newPkg)) {
368 mQueriesViaIntent.add(existingSetting.appId, newPkgSetting.appId);
Patrick Baumann88318e02019-11-04 14:22:25 -0800369 }
Alan Stokesa547c5c62020-01-20 11:36:08 +0000370 if (canQueryViaPackage(existingPkg, newPkg)
371 || canQueryAsInstaller(existingSetting, newPkg)) {
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800372 mQueriesViaPackage.add(existingSetting.appId, newPkgSetting.appId);
373 }
374 }
375 // now we'll evaluate our new package's ability to see existing packages
376 if (!mForceQueryable.contains(existingSetting.appId)) {
377 if (canQueryViaIntent(newPkg, existingPkg)) {
378 mQueriesViaIntent.add(newPkgSetting.appId, existingSetting.appId);
379 }
Alan Stokesa547c5c62020-01-20 11:36:08 +0000380 if (canQueryViaPackage(newPkg, existingPkg)
381 || canQueryAsInstaller(newPkgSetting, existingPkg)) {
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800382 mQueriesViaPackage.add(newPkgSetting.appId, existingSetting.appId);
Patrick Baumann88318e02019-11-04 14:22:25 -0800383 }
384 }
385 }
Winson Chiu2fdaf812019-12-13 20:01:15 +0000386
387 int existingSize = existingSettings.size();
388 ArrayMap<String, AndroidPackage> existingPkgs = new ArrayMap<>(existingSize);
389 for (int index = 0; index < existingSize; index++) {
390 PackageSetting pkgSetting = existingSettings.valueAt(index);
391 if (pkgSetting.pkg != null) {
392 existingPkgs.put(pkgSetting.name, pkgSetting.pkg);
393 }
394 }
395 mOverlayReferenceMapper.addPkg(newPkgSetting.pkg, existingPkgs);
Patrick Baumann88318e02019-11-04 14:22:25 -0800396 } finally {
397 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700398 }
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700399 }
400
Patrick Baumann051d75c2020-01-10 15:13:48 -0800401 private static boolean isSystemSigned(@NonNull PackageParser.SigningDetails sysSigningDetails,
402 PackageSetting pkgSetting) {
403 return pkgSetting.isSystem()
404 && pkgSetting.signatures.mSigningDetails.signaturesMatchExactly(sysSigningDetails);
405 }
406
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700407 /**
408 * Removes a package for consideration when filtering visibility between apps.
409 *
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800410 * @param setting the setting of the package being removed.
411 * @param allUsers array of all current users on device.
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700412 */
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800413 public void removePackage(PackageSetting setting, int[] allUsers,
414 ArrayMap<String, PackageSetting> existingSettings) {
415 mForceQueryable.remove(setting.appId);
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700416
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800417 for (int u = 0; u < allUsers.length; u++) {
418 final int userId = allUsers[u];
419 final int removingUid = UserHandle.getUid(userId, setting.appId);
420 mImplicitlyQueryable.remove(removingUid);
421 for (int i = mImplicitlyQueryable.size() - 1; i >= 0; i--) {
422 mImplicitlyQueryable.remove(mImplicitlyQueryable.keyAt(i), removingUid);
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700423 }
424 }
425
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800426 mQueriesViaIntent.remove(setting.appId);
427 for (int i = mQueriesViaIntent.size() - 1; i >= 0; i--) {
428 mQueriesViaIntent.remove(mQueriesViaIntent.keyAt(i), setting.appId);
429 }
430 mQueriesViaPackage.remove(setting.appId);
431 for (int i = mQueriesViaPackage.size() - 1; i >= 0; i--) {
432 mQueriesViaPackage.remove(mQueriesViaPackage.keyAt(i), setting.appId);
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700433 }
434
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800435 // re-add other shared user members to re-establish visibility between them and other
436 // packages
437 if (setting.sharedUser != null) {
438 for (int i = setting.sharedUser.packages.size() - 1; i >= 0; i--) {
439 if (setting.sharedUser.packages.valueAt(i) == setting) {
440 continue;
441 }
442 addPackage(setting.sharedUser.packages.valueAt(i), existingSettings);
443 }
444 }
Winson Chiu2fdaf812019-12-13 20:01:15 +0000445
446 mOverlayReferenceMapper.removePkg(setting.name);
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700447 }
448
449 /**
450 * Returns true if the calling package should not be able to see the target package, false if no
451 * filtering should be done.
452 *
453 * @param callingUid the uid of the caller attempting to access a package
454 * @param callingSetting the setting attempting to access a package or null if it could not be
455 * found
456 * @param targetPkgSetting the package being accessed
457 * @param userId the user in which this access is being attempted
458 */
459 public boolean shouldFilterApplication(int callingUid, @Nullable SettingBase callingSetting,
460 PackageSetting targetPkgSetting, int userId) {
Patrick Baumann88318e02019-11-04 14:22:25 -0800461 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "shouldFilterApplication");
462 try {
Winson Chiu2fdaf812019-12-13 20:01:15 +0000463 if (!shouldFilterApplicationInternal(callingUid, callingSetting, targetPkgSetting,
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800464 userId)) {
465 return false;
466 }
467 if (DEBUG_LOGGING) {
468 log(callingSetting, targetPkgSetting,
469 DEBUG_ALLOW_ALL ? "ALLOWED" : "BLOCKED", new RuntimeException());
470 }
471 return !DEBUG_ALLOW_ALL;
472 } finally {
473 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
474 }
475 }
476
Winson Chiu2fdaf812019-12-13 20:01:15 +0000477 private boolean shouldFilterApplicationInternal(int callingUid, SettingBase callingSetting,
478 PackageSetting targetPkgSetting, int userId) {
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800479 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "shouldFilterApplicationInternal");
480 try {
Patrick Baumann88318e02019-11-04 14:22:25 -0800481 final boolean featureEnabled = mFeatureConfig.isGloballyEnabled();
482 if (!featureEnabled) {
483 if (DEBUG_LOGGING) {
484 Slog.d(TAG, "filtering disabled; skipped");
485 }
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700486 return false;
487 }
Patrick Baumann88318e02019-11-04 14:22:25 -0800488 if (callingUid < Process.FIRST_APPLICATION_UID) {
489 if (DEBUG_LOGGING) {
490 Slog.d(TAG, "filtering skipped; " + callingUid + " is system");
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700491 }
Patrick Baumann88318e02019-11-04 14:22:25 -0800492 return false;
493 }
494 if (callingSetting == null) {
495 Slog.wtf(TAG, "No setting found for non system uid " + callingUid);
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700496 return true;
497 }
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800498 final PackageSetting callingPkgSetting;
499 final ArraySet<PackageSetting> callingSharedPkgSettings;
Patrick Baumann2eebc9e2019-11-22 09:29:09 -0800500 Trace.beginSection("callingSetting instanceof");
Patrick Baumann88318e02019-11-04 14:22:25 -0800501 if (callingSetting instanceof PackageSetting) {
502 callingPkgSetting = (PackageSetting) callingSetting;
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800503 callingSharedPkgSettings = null;
504 } else {
505 callingPkgSetting = null;
506 callingSharedPkgSettings = ((SharedUserSetting) callingSetting).packages;
507 }
Patrick Baumann2eebc9e2019-11-22 09:29:09 -0800508 Trace.endSection();
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800509
510 if (callingPkgSetting != null) {
511 if (!mFeatureConfig.packageIsEnabled(callingPkgSetting.pkg)) {
512 if (DEBUG_LOGGING) {
513 log(callingSetting, targetPkgSetting, "DISABLED");
514 }
Patrick Baumann88318e02019-11-04 14:22:25 -0800515 return false;
516 }
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800517 } else {
518 for (int i = callingSharedPkgSettings.size() - 1; i >= 0; i--) {
519 if (!mFeatureConfig.packageIsEnabled(callingSharedPkgSettings.valueAt(i).pkg)) {
520 if (DEBUG_LOGGING) {
521 log(callingSetting, targetPkgSetting, "DISABLED");
Patrick Baumann88318e02019-11-04 14:22:25 -0800522 }
Patrick Baumanna72e6742019-11-06 08:26:56 -0800523 return false;
524 }
525 }
Patrick Baumanna72e6742019-11-06 08:26:56 -0800526 }
527
Patrick Baumann88318e02019-11-04 14:22:25 -0800528 // This package isn't technically installed and won't be written to settings, so we can
529 // treat it as filtered until it's available again.
Winson655a5b92019-10-23 10:49:32 -0700530 final AndroidPackage targetPkg = targetPkgSetting.pkg;
Patrick Baumann88318e02019-11-04 14:22:25 -0800531 if (targetPkg == null) {
532 if (DEBUG_LOGGING) {
533 Slog.wtf(TAG, "shouldFilterApplication: " + "targetPkg is null");
534 }
535 return true;
536 }
Patrick Baumann71bb3f32020-01-15 15:36:57 -0800537 if (targetPkg.isStaticSharedLibrary()) {
538 // not an app, this filtering takes place at a higher level
539 return false;
540 }
Winson655a5b92019-10-23 10:49:32 -0700541 final String targetName = targetPkg.getPackageName();
Patrick Baumann2eebc9e2019-11-22 09:29:09 -0800542 Trace.beginSection("getAppId");
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800543 final int callingAppId;
544 if (callingPkgSetting != null) {
545 callingAppId = callingPkgSetting.appId;
546 } else {
547 callingAppId = callingSharedPkgSettings.valueAt(0).appId; // all should be the same
548 }
549 final int targetAppId = targetPkgSetting.appId;
Patrick Baumann2eebc9e2019-11-22 09:29:09 -0800550 Trace.endSection();
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800551 if (callingAppId == targetAppId) {
Patrick Baumann88318e02019-11-04 14:22:25 -0800552 if (DEBUG_LOGGING) {
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800553 log(callingSetting, targetPkgSetting, "same app id");
Patrick Baumann88318e02019-11-04 14:22:25 -0800554 }
555 return false;
556 }
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800557
Patrick Baumann2eebc9e2019-11-22 09:29:09 -0800558 try {
559 Trace.beginSection("hasPermission");
560 if (callingSetting.getPermissionsState().hasPermission(
561 Manifest.permission.QUERY_ALL_PACKAGES, UserHandle.getUserId(callingUid))) {
562 if (DEBUG_LOGGING) {
563 log(callingSetting, targetPkgSetting, "has query-all permission");
564 }
565 return false;
Patrick Baumanna72e6742019-11-06 08:26:56 -0800566 }
Patrick Baumann2eebc9e2019-11-22 09:29:09 -0800567 } finally {
568 Trace.endSection();
Patrick Baumanna72e6742019-11-06 08:26:56 -0800569 }
Patrick Baumann2eebc9e2019-11-22 09:29:09 -0800570 try {
571 Trace.beginSection("mForceQueryable");
572 if (mForceQueryable.contains(targetAppId)) {
573 if (DEBUG_LOGGING) {
574 log(callingSetting, targetPkgSetting, "force queryable");
575 }
576 return false;
Patrick Baumann88318e02019-11-04 14:22:25 -0800577 }
Patrick Baumann2eebc9e2019-11-22 09:29:09 -0800578 } finally {
579 Trace.endSection();
Patrick Baumann88318e02019-11-04 14:22:25 -0800580 }
Patrick Baumann2eebc9e2019-11-22 09:29:09 -0800581 try {
582 Trace.beginSection("mQueriesViaPackage");
583 if (mQueriesViaPackage.contains(callingAppId, targetAppId)) {
Patrick Baumann2eebc9e2019-11-22 09:29:09 -0800584 if (DEBUG_LOGGING) {
585 log(callingSetting, targetPkgSetting, "queries package");
586 }
587 return false;
Patrick Baumann88318e02019-11-04 14:22:25 -0800588 }
Patrick Baumann2eebc9e2019-11-22 09:29:09 -0800589 } finally {
590 Trace.endSection();
591 }
592 try {
593 Trace.beginSection("mQueriesViaIntent");
594 if (mQueriesViaIntent.contains(callingAppId, targetAppId)) {
595 if (DEBUG_LOGGING) {
596 log(callingSetting, targetPkgSetting, "queries intent");
597 }
598 return false;
Patrick Baumann88318e02019-11-04 14:22:25 -0800599 }
Patrick Baumann2eebc9e2019-11-22 09:29:09 -0800600 } finally {
601 Trace.endSection();
Patrick Baumann88318e02019-11-04 14:22:25 -0800602 }
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800603
Patrick Baumann2eebc9e2019-11-22 09:29:09 -0800604 try {
605 Trace.beginSection("mImplicitlyQueryable");
606 final int targetUid = UserHandle.getUid(userId, targetAppId);
607 if (mImplicitlyQueryable.contains(callingUid, targetUid)) {
608 if (DEBUG_LOGGING) {
609 log(callingSetting, targetPkgSetting, "implicitly queryable for user");
610 }
611 return false;
Patrick Baumann88318e02019-11-04 14:22:25 -0800612 }
Patrick Baumann2eebc9e2019-11-22 09:29:09 -0800613 } finally {
614 Trace.endSection();
Patrick Baumann88318e02019-11-04 14:22:25 -0800615 }
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800616 if (callingPkgSetting != null) {
617 if (callingPkgInstruments(callingPkgSetting, targetPkgSetting, targetName)) {
618 return false;
619 }
620 } else {
621 for (int i = callingSharedPkgSettings.size() - 1; i >= 0; i--) {
622 if (callingPkgInstruments(callingSharedPkgSettings.valueAt(i),
623 targetPkgSetting, targetName)) {
Patrick Baumann88318e02019-11-04 14:22:25 -0800624 return false;
625 }
626 }
627 }
Winson Chiu2fdaf812019-12-13 20:01:15 +0000628
629 if (callingSharedPkgSettings != null) {
630 int size = callingSharedPkgSettings.size();
631 for (int index = 0; index < size; index++) {
632 PackageSetting pkgSetting = callingSharedPkgSettings.valueAt(index);
633 if (mOverlayReferenceMapper.isValidActor(targetName, pkgSetting.name)) {
634 if (DEBUG_LOGGING) {
635 log(callingPkgSetting, targetPkgSetting,
636 "matches shared user of package that acts on target of "
637 + "overlay");
638 }
639 return false;
640 }
641 }
642 } else {
643 if (mOverlayReferenceMapper.isValidActor(targetName, callingPkgSetting.name)) {
644 if (DEBUG_LOGGING) {
645 log(callingPkgSetting, targetPkgSetting, "acts on target of overlay");
646 }
647 return false;
648 }
649 }
650
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700651 return true;
Patrick Baumann88318e02019-11-04 14:22:25 -0800652 } finally {
653 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700654 }
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700655 }
656
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800657 private static boolean callingPkgInstruments(PackageSetting callingPkgSetting,
658 PackageSetting targetPkgSetting,
659 String targetName) {
Patrick Baumann2eebc9e2019-11-22 09:29:09 -0800660 try {
661 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "callingPkgInstruments");
662 final List<ComponentParseUtils.ParsedInstrumentation> inst =
663 callingPkgSetting.pkg.getInstrumentations();
664 for (int i = ArrayUtils.size(inst) - 1; i >= 0; i--) {
665 if (Objects.equals(inst.get(i).getTargetPackage(), targetName)) {
666 if (DEBUG_LOGGING) {
667 log(callingPkgSetting, targetPkgSetting, "instrumentation");
668 }
669 return true;
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800670 }
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800671 }
Patrick Baumann2eebc9e2019-11-22 09:29:09 -0800672 return false;
673 } finally {
674 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800675 }
Patrick Baumann0c9257b2019-08-20 12:50:37 -0700676 }
677
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800678 private static void log(SettingBase callingPkgSetting, PackageSetting targetPkgSetting,
679 String description) {
680 log(callingPkgSetting, targetPkgSetting, description, null);
681 }
682
683 private static void log(SettingBase callingPkgSetting, PackageSetting targetPkgSetting,
684 String description, Throwable throwable) {
685 Slog.wtf(TAG,
Patrick Baumanne2d9d712020-01-10 13:32:09 -0800686 "interaction: " + callingPkgSetting
Patrick Baumannefb37362020-01-14 14:49:07 -0800687 + " -> " + targetPkgSetting + " "
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800688 + description, throwable);
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700689 }
690
Patrick Baumanna7ad66d2019-08-12 11:11:08 -0700691 public void dumpQueries(
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800692 PrintWriter pw, PackageManagerService pms, @Nullable Integer filteringAppId,
693 DumpState dumpState,
Patrick Baumanna7ad66d2019-08-12 11:11:08 -0700694 int[] users) {
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800695 final SparseArray<String> cache = new SparseArray<>();
696 ToString<Integer> expandPackages = input -> {
697 String cachedValue = cache.get(input);
698 if (cachedValue == null) {
699 final String[] packagesForUid = pms.getPackagesForUid(input);
700 if (packagesForUid == null) {
701 cachedValue = "[unknown app id " + input + "]";
702 } else {
703 cachedValue = packagesForUid.length == 1 ? packagesForUid[0]
704 : "[" + TextUtils.join(",", packagesForUid) + "]";
705 }
706 cache.put(input, cachedValue);
707 }
708 return cachedValue;
709 };
Patrick Baumanna7ad66d2019-08-12 11:11:08 -0700710 pw.println();
711 pw.println("Queries:");
712 dumpState.onTitlePrinted();
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800713 if (!mFeatureConfig.isGloballyEnabled()) {
714 pw.println(" DISABLED");
715 if (!DEBUG_LOGGING) {
716 return;
717 }
718 }
Patrick Baumanna7ad66d2019-08-12 11:11:08 -0700719 pw.println(" system apps queryable: " + mSystemAppsQueryable);
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800720 dumpPackageSet(pw, filteringAppId, mForceQueryable, "forceQueryable", " ", expandPackages);
Patrick Baumanna7ad66d2019-08-12 11:11:08 -0700721 pw.println(" queries via package name:");
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800722 dumpQueriesMap(pw, filteringAppId, mQueriesViaPackage, " ", expandPackages);
Patrick Baumanna7ad66d2019-08-12 11:11:08 -0700723 pw.println(" queries via intent:");
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800724 dumpQueriesMap(pw, filteringAppId, mQueriesViaIntent, " ", expandPackages);
Patrick Baumanna7ad66d2019-08-12 11:11:08 -0700725 pw.println(" queryable via interaction:");
726 for (int user : users) {
727 pw.append(" User ").append(Integer.toString(user)).println(":");
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800728 dumpQueriesMap(pw,
729 filteringAppId == null ? null : UserHandle.getUid(user, filteringAppId),
730 mImplicitlyQueryable, " ", expandPackages);
Patrick Baumanna7ad66d2019-08-12 11:11:08 -0700731 }
732 }
733
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800734 private static void dumpQueriesMap(PrintWriter pw, @Nullable Integer filteringId,
735 SparseSetArray<Integer> queriesMap, String spacing,
736 @Nullable ToString<Integer> toString) {
737 for (int i = 0; i < queriesMap.size(); i++) {
738 Integer callingId = queriesMap.keyAt(i);
739 if (Objects.equals(callingId, filteringId)) {
740 // don't filter target package names if the calling is filteringId
741 dumpPackageSet(
742 pw, null /*filteringId*/, queriesMap.get(callingId),
743 toString == null
744 ? callingId.toString()
745 : toString.toString(callingId),
746 spacing, toString);
Patrick Baumanna7ad66d2019-08-12 11:11:08 -0700747 } else {
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800748 dumpPackageSet(
749 pw, filteringId, queriesMap.get(callingId),
750 toString == null
751 ? callingId.toString()
752 : toString.toString(callingId),
753 spacing, toString);
Patrick Baumanna7ad66d2019-08-12 11:11:08 -0700754 }
755 }
756 }
757
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800758 private interface ToString<T> {
759 String toString(T input);
760 }
761
762 private static <T> void dumpPackageSet(PrintWriter pw, @Nullable T filteringId,
763 Set<T> targetPkgSet, String subTitle, String spacing,
764 @Nullable ToString<T> toString) {
Patrick Baumanna7ad66d2019-08-12 11:11:08 -0700765 if (targetPkgSet != null && targetPkgSet.size() > 0
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800766 && (filteringId == null || targetPkgSet.contains(filteringId))) {
Patrick Baumanna7ad66d2019-08-12 11:11:08 -0700767 pw.append(spacing).append(subTitle).println(":");
Patrick Baumannb9a49f42019-11-13 16:42:18 -0800768 for (T item : targetPkgSet) {
769 if (filteringId == null || Objects.equals(filteringId, item)) {
770 pw.append(spacing).append(" ")
771 .println(toString == null ? item : toString.toString(item));
Patrick Baumanna7ad66d2019-08-12 11:11:08 -0700772 }
773 }
774 }
775 }
Patrick Baumann6c1c8092019-06-27 14:55:44 -0700776}