1 /* 2 * Copyright (C) 2024 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 package com.android.adservices.service.common; 17 18 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__PACKAGE_DENY_PROCESS_ERROR_DISABLED; 19 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__PACKAGE_DENY_PROCESS_ERROR_FAILED_FILTERING_INSTALLED_PACKAGES; 20 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__PACKAGE_DENY_PROCESS_ERROR_FAILED_READING_CACHE; 21 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__PACKAGE_DENY_PROCESS_ERROR_FAILED_READING_FILE; 22 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__PACKAGE_DENY_PROCESS_ERROR_FAILED_UPDATING_CACHE; 23 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__PACKAGE_DENY_PROCESS_ERROR_FAILURE_UNKNOWN; 24 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__PACKAGE_DENY_PROCESS_ERROR_NO_FILE_FOUND; 25 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__COMMON; 26 27 import android.annotation.Nullable; 28 import android.content.pm.ApplicationInfo; 29 import android.content.pm.PackageManager; 30 import android.net.Uri; 31 import android.os.Build; 32 33 import androidx.annotation.NonNull; 34 import androidx.annotation.RequiresApi; 35 import androidx.datastore.guava.GuavaDataStore; 36 37 import com.android.adservices.LogUtil; 38 import com.android.adservices.concurrency.AdServicesExecutors; 39 import com.android.adservices.download.MobileDataDownloadFactory; 40 import com.android.adservices.errorlogging.ErrorLogUtil; 41 import com.android.adservices.service.Flags; 42 import com.android.adservices.service.FlagsFactory; 43 import com.android.adservices.service.common.compat.FileCompatUtils; 44 import com.android.adservices.service.proto.ApiDenyGroupsForPackage; 45 import com.android.adservices.service.proto.ApiGroupsCache; 46 import com.android.adservices.service.proto.PackageToApiDenyGroupsCacheMap; 47 import com.android.adservices.service.proto.PackageToApiDenyGroupsMap; 48 import com.android.adservices.service.proto.PackageType; 49 import com.android.adservices.shared.common.ApplicationContextSingleton; 50 import com.android.adservices.shared.datastore.ProtoSerializer; 51 import com.android.internal.annotations.VisibleForTesting; 52 53 import com.google.android.libraries.mobiledatadownload.GetFileGroupRequest; 54 import com.google.android.libraries.mobiledatadownload.MobileDataDownload; 55 import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage; 56 import com.google.android.libraries.mobiledatadownload.file.openers.ReadStreamOpener; 57 import com.google.common.util.concurrent.FluentFuture; 58 import com.google.common.util.concurrent.Futures; 59 import com.google.common.util.concurrent.ListenableFuture; 60 import com.google.mobiledatadownload.ClientConfigProto; 61 import com.google.protobuf.ExtensionRegistryLite; 62 63 import java.io.IOException; 64 import java.util.Collections; 65 import java.util.List; 66 import java.util.Map; 67 import java.util.Set; 68 import java.util.stream.Collectors; 69 70 @RequiresApi(Build.VERSION_CODES.S) 71 public final class AdPackageDenyResolver { 72 private static final String GROUP_NAME = "package-deny"; 73 private static final String PACKAGE_DENY_DATA_STORE = 74 FileCompatUtils.getAdservicesFilename("package_deny_data_store"); 75 private final boolean mEnablePackageDeny; 76 @Nullable private final MobileDataDownload mMobileDataDownload; 77 @Nullable private final SynchronousFileStorage mFileStorage; 78 79 @Nullable 80 private final GuavaDataStore<PackageToApiDenyGroupsCacheMap> mPackageDenyCacheDataStore; 81 82 // Lazy initialization holder class idiom for static fields as described in Effective Java Item 83 // 83 - this is needed because otherwise the singleton would be initialized in unit tests, even 84 // when they (correctly) call newInstance() instead of getInstance(). 85 private static final class FieldHolder { 86 private static final AdPackageDenyResolver sSingleton; 87 88 static { // static initialization 89 sSingleton = newInstance(); 90 } 91 } 92 AdPackageDenyResolver( @ullable MobileDataDownload mobileDataDownload, @Nullable SynchronousFileStorage synchronousFileStorage, @Nullable GuavaDataStore<PackageToApiDenyGroupsCacheMap> packageDenyCacheDataStore, boolean enablePackageDeny)93 private AdPackageDenyResolver( 94 @Nullable MobileDataDownload mobileDataDownload, 95 @Nullable SynchronousFileStorage synchronousFileStorage, 96 @Nullable GuavaDataStore<PackageToApiDenyGroupsCacheMap> packageDenyCacheDataStore, 97 boolean enablePackageDeny) { 98 LogUtil.d("initializing AdPackageDenyResolver instance"); 99 this.mMobileDataDownload = mobileDataDownload; 100 this.mFileStorage = synchronousFileStorage; 101 this.mPackageDenyCacheDataStore = packageDenyCacheDataStore; 102 mEnablePackageDeny = enablePackageDeny; 103 } 104 105 /** 106 * Creates a new instance of {@link AdPackageDenyResolver}. 107 * 108 * <p>This method uses the `enablePackageDenyMdd` flag from {@link FlagsFactory} to determine 109 * how the `AdPackageDenyResolver` should be initialized. 110 * 111 * <p>If the flag is enabled: - Obtains an MDD instance from {@link MobileDataDownloadFactory}. 112 * - Retrieves a file storage instance from {@link MobileDataDownloadFactory}. - Uses a 113 * `PackageToApiDenyGroupsCacheMapDataStore` (obtained via 114 * `getPackageToApiDenyGroupsCacheMapDataStore()`). - Sets enablement flag to `true`. 115 * 116 * <p>If the flag is disabled: - It creates an instance with all dependencies set to `null` and 117 * the enablement flag set to `false`. 118 * 119 * @return A new instance of `AdPackageDenyResolver` configured based on the 120 * `enablePackageDenyMdd` flag. 121 */ newInstance()122 private static AdPackageDenyResolver newInstance() { 123 try { 124 Flags flags = FlagsFactory.getFlags(); 125 if (flags.getEnablePackageDenyService()) { 126 return new AdPackageDenyResolver( 127 MobileDataDownloadFactory.getMdd(flags), 128 MobileDataDownloadFactory.getFileStorage(), 129 getPackageToApiDenyGroupsCacheMapDataStore(), 130 true); 131 } 132 } catch (Exception e) { 133 LogUtil.e("Error initializing AdPackageDenyResolver %s", e.getMessage()); 134 } 135 return new AdPackageDenyResolver(null, null, null, false); 136 } 137 138 @VisibleForTesting newInstanceForTests( @onNull MobileDataDownload mobileDataDownload, @NonNull SynchronousFileStorage synchronousFileStorage, @NonNull GuavaDataStore<PackageToApiDenyGroupsCacheMap> packageDataStore, boolean enablePackageDeny)139 static AdPackageDenyResolver newInstanceForTests( 140 @NonNull MobileDataDownload mobileDataDownload, 141 @NonNull SynchronousFileStorage synchronousFileStorage, 142 @NonNull GuavaDataStore<PackageToApiDenyGroupsCacheMap> packageDataStore, 143 boolean enablePackageDeny) { 144 return new AdPackageDenyResolver( 145 mobileDataDownload, synchronousFileStorage, packageDataStore, enablePackageDeny); 146 } 147 148 private static GuavaDataStore<PackageToApiDenyGroupsCacheMap> getPackageToApiDenyGroupsCacheMapDataStore()149 getPackageToApiDenyGroupsCacheMapDataStore() { 150 return new GuavaDataStore.Builder( 151 ApplicationContextSingleton.get(), 152 PACKAGE_DENY_DATA_STORE, 153 new ProtoSerializer<PackageToApiDenyGroupsCacheMap>( 154 PackageToApiDenyGroupsCacheMap.getDefaultInstance(), 155 ExtensionRegistryLite.getEmptyRegistry())) 156 .setExecutor(AdServicesExecutors.getBackgroundExecutor()) 157 .build(); 158 } 159 160 /** 161 * Returns the singleton instance of {@link AdPackageDenyResolver}. 162 * 163 * <p>This method provides access to the single instance of the `AdPackageDenyResolver` class, 164 * ensuring that all parts of the application use the same resolver object. 165 * 166 * @return The singleton instance of `AdPackageDenyResolver`. 167 */ getInstance()168 public static AdPackageDenyResolver getInstance() { 169 return FieldHolder.sSingleton; 170 } 171 172 /** 173 * Determines whether an app or SDK should be denied access based on the provided API groups. 174 * 175 * <p>This method checks if access should be denied for a given caller app or SDK based on their 176 * usage of specific API groups. It uses a package deny list loaded from MDD and cached in 177 * `mPackageDenyCacheDataStore`. 178 * 179 * <p>The logic works as follows: 1. **Feature Check:** If the package deny feature 180 * (`mEnablePackageDeny`) is disabled, it logs a message and immediately returns `false`. 2. 181 * **Input Validation:** If `apiGroups` is null or empty, or both `callerAppName` and 182 * `callerSdkName` are null, it returns `false`. 3. **Deny List Check:** It retrieves the 183 * package deny list data from the cache (`mPackageDenyCacheDataStore`). 4. **App and SDK 184 * Check:** It checks if the `callerAppName` or `callerSdkName` exists in the deny list and if 185 * there's any overlap between the provided `apiGroups` and the API groups associated with the 186 * caller app or SDK in the deny list. If an overlap is found, it means the package should be 187 * denied access. 188 * 189 * @param callerAppName The name of the calling app. Can be null or empty. 190 * @param callerSdkName The name of the calling SDK. Can be null or empty. 191 * @param apiGroups The set of API groups being accessed. 192 * @return a {@link ListenableFuture} representing the result of the deny list check. The future 193 * yields `true` if the package should be denied access, `false` otherwise. 194 */ shouldDenyPackage( String callerAppName, String callerSdkName, Set<String> apiGroups)195 public ListenableFuture<Boolean> shouldDenyPackage( 196 String callerAppName, String callerSdkName, Set<String> apiGroups) { 197 if (!mEnablePackageDeny) { 198 LogUtil.e( 199 "shouldDenyPackage() is called on package-based deny-list that is disabled" 200 + " with calling app %s, calling sdk %s and api groups %s", 201 callerAppName, callerSdkName, apiGroups); 202 PackageDenyMddProcessStatus.logError(PackageDenyMddProcessStatus.DISABLED); 203 return Futures.immediateFuture(false); 204 } 205 if (apiGroups == null 206 || apiGroups.isEmpty() 207 || (callerAppName == null && callerSdkName == null)) { 208 return Futures.immediateFuture(false); 209 } 210 return Futures.transform( 211 mPackageDenyCacheDataStore.getDataAsync(), 212 packageToApiDenyGroupsCacheMap -> 213 (callerAppName != null 214 && !callerAppName.isBlank() 215 && !Collections.disjoint( 216 apiGroups, 217 packageToApiDenyGroupsCacheMap 218 .getAppToApiDenyGroupsCacheMap() 219 .getOrDefault( 220 callerAppName, 221 ApiGroupsCache.getDefaultInstance()) 222 .getApiGroupList())) 223 || (callerSdkName != null 224 && !callerSdkName.isBlank() 225 && !Collections.disjoint( 226 apiGroups, 227 packageToApiDenyGroupsCacheMap 228 .getSdkToApiDenyGroupsCacheMap() 229 .getOrDefault( 230 callerSdkName, 231 ApiGroupsCache.getDefaultInstance()) 232 .getApiGroupList())), 233 AdServicesExecutors.getBackgroundExecutor()); 234 } 235 236 /** 237 * Loads and processes package deny data from Mobile Data Download (MDD). 238 * 239 * <p>This method orchestrates the retrieval and processing of package deny data from MDD. It 240 * performs the following steps asynchronously: 1. **Checks Feature Flag:** Verifies if the 241 * package deny feature is enabled (`mEnablePackageDeny`). If disabled, it returns a {@link 242 * PackageDenyMddProcessStatus#DISABLED} status. 2. **Fetches MDD File Group:** Initiates a 243 * request to MDD to fetch the file group associated with the package deny data (using 244 * `mMobileDataDownload::getFileGroup`). 3. **Extracts MDD File:** Obtains the actual file from 245 * the fetched file group (using `this::getMddFile`). 4. **Parses the File:** Parses the content 246 * of the MDD file, converting it into a usable data structure (using `this::parseFile`). 5. 247 * **Filters Data:** Filters the parsed data to retain only information relevant to installed 248 * packages (using `this::filterMddDataToInstalledPackages`). 6. **Updates Cache:** 249 * Asynchronously updates the package deny cache data store (`mPackageDenyCacheDataStore`) with 250 * the processed data. 7. **Handles Errors:** Includes error handling for `PackageDenyException` 251 * and general `Exception` to log issues and return appropriate {@link 252 * PackageDenyMddProcessStatus} values. 253 * 254 * @return A {@link ListenableFuture} representing the eventual result of the MDD data loading 255 * and processing operation. The future yields a {@link PackageDenyMddProcessStatus} 256 * indicating the outcome of the process (success, failure, or disabled). 257 */ loadDenyDataFromMdd()258 public ListenableFuture<PackageDenyMddProcessStatus> loadDenyDataFromMdd() { 259 LogUtil.d("Executing load deny data from mdd download"); 260 if (!mEnablePackageDeny) { 261 LogUtil.d("Package deny service flag is false, returning..."); 262 PackageDenyMddProcessStatus.logError(PackageDenyMddProcessStatus.DISABLED); 263 return Futures.immediateFuture(PackageDenyMddProcessStatus.DISABLED); 264 } 265 return FluentFuture.from( 266 Futures.immediateFuture( 267 GetFileGroupRequest.newBuilder().setGroupName(GROUP_NAME).build())) 268 .transformAsync( 269 mMobileDataDownload::getFileGroup, 270 AdServicesExecutors.getLightWeightExecutor()) 271 .transform(this::getMddFile, AdServicesExecutors.getLightWeightExecutor()) 272 .transform(this::parseFile, AdServicesExecutors.getBackgroundExecutor()) 273 .transform(this::convertToCacheMap, AdServicesExecutors.getLightWeightExecutor()) 274 .transformAsync( 275 map -> mPackageDenyCacheDataStore.updateDataAsync(data -> map), 276 AdServicesExecutors.getBackgroundExecutor()) 277 .transform( 278 // TODO (b/365605754) add metrics for success 279 x -> PackageDenyMddProcessStatus.SUCCESS, 280 AdServicesExecutors.getLightWeightExecutor()) 281 .catching( 282 PackageDenyException.class, 283 e -> { 284 LogUtil.e( 285 "PackageDenyException: %s with cause: %s", 286 e.getMessage(), e.getCause()); 287 PackageDenyMddProcessStatus packageDenyMddProcessStatus = 288 PackageDenyMddProcessStatus.valueOf(e.getMessage()); 289 PackageDenyMddProcessStatus.logError(packageDenyMddProcessStatus); 290 return packageDenyMddProcessStatus; 291 }, 292 AdServicesExecutors.getLightWeightExecutor()) 293 .catching( 294 Exception.class, 295 e -> { 296 LogUtil.e("Failure in deny package mdd process %s", e.getMessage()); 297 PackageDenyMddProcessStatus.logError( 298 PackageDenyMddProcessStatus.FAILED_UPDATING_CACHE); 299 return PackageDenyMddProcessStatus.FAILED_UPDATING_CACHE; 300 }, 301 AdServicesExecutors.getLightWeightExecutor()); 302 } 303 getMddFile( ClientConfigProto.ClientFileGroup clientFileGroup)304 private ClientConfigProto.ClientFile getMddFile( 305 ClientConfigProto.ClientFileGroup clientFileGroup) { 306 if (clientFileGroup == null) { 307 LogUtil.d("MDD has not downloaded the package deny file yet for group %s", GROUP_NAME); 308 throw new PackageDenyException(PackageDenyMddProcessStatus.NO_FILE_FOUND); 309 } 310 return clientFileGroup.getFileList().stream() 311 .filter(file -> file.getFileId().endsWith(".pb")) 312 .findFirst() 313 .orElseThrow( 314 () -> new PackageDenyException(PackageDenyMddProcessStatus.NO_FILE_FOUND)); 315 } 316 parseFile(ClientConfigProto.ClientFile clientFile)317 private PackageToApiDenyGroupsMap parseFile(ClientConfigProto.ClientFile clientFile) { 318 try { 319 return PackageToApiDenyGroupsMap.parseFrom( 320 mFileStorage.open( 321 Uri.parse(clientFile.getFileUri()), ReadStreamOpener.create())); 322 } catch (IOException e) { 323 throw new PackageDenyException(PackageDenyMddProcessStatus.FAILED_READING_FILE, e); 324 } 325 } 326 327 /** 328 * Converts the PackageToApiDenyGroupsMap to PackageToApiDenyGroupsCacheMap based on whether the 329 * package is installed. 330 * 331 * <p>If the enableInstalledPackageFilter flag is enabled, this method retrieves a map of 332 * installed packages and their version codes, and then filters the input 333 * packageToApiDenyGroupsMap to only include entries for installed packages. If the flag is 334 * disabled, the input map is returned without filtering and does not deny based on package 335 * version. 336 * 337 * @param packageToApiDenyGroupsMap The input PackageToApiDenyGroupsMap to filter. 338 * @return The converted PackageToApiDenyGroupsCacheMap. 339 * @throws PackageDenyException if an error occurs during the filtering process, specifically 340 * with PackageDenyMddProcessStatus#FAILED_FILTERING_INSTALLED_PACKAGES status. 341 */ 342 @VisibleForTesting convertToCacheMap( PackageToApiDenyGroupsMap packageToApiDenyGroupsMap)343 PackageToApiDenyGroupsCacheMap convertToCacheMap( 344 PackageToApiDenyGroupsMap packageToApiDenyGroupsMap) { 345 try { 346 if (FlagsFactory.getFlags().getPackageDenyEnableInstalledPackageFilter()) { 347 Map<String, Long> installedPackages = 348 getInstalledPackageNameToVersionCodeMap( 349 ApplicationContextSingleton.get().getPackageManager()); 350 return getCacheMapFilteredByInstalledPackages( 351 packageToApiDenyGroupsMap, installedPackages); 352 } else { 353 LogUtil.d("Package installed filter is disabled"); 354 return getCacheMapWithoutFilter(packageToApiDenyGroupsMap); 355 } 356 357 } catch (Exception e) { 358 throw new PackageDenyException( 359 PackageDenyMddProcessStatus.FAILED_FILTERING_INSTALLED_PACKAGES, e); 360 } 361 } 362 363 /** 364 * Filters a PackageToApiDenyGroupsMap to include only installed packages and transforms it into 365 * a PackageToApiDenyGroupsCacheMap with optimized API group representations. 366 * 367 * <p>This method takes a PackageToApiDenyGroupsMap and a map of installed packages with their 368 * version codes. It performs the following steps: 1. **Filters the input map:** Keeps only the 369 * entries corresponding to installed packages. 2. **Groups by package type:** Groups the 370 * entries into two categories: APP and SDK. 3. **Transforms into nested maps:** Creates a 371 * nested map structure where the outer map keys are package types (`PackageType`), and the 372 * inner map keys are package names with values as lists of applicable API groups. This is done 373 * by calling `getApiGroupsForInstalledPackages` to determine the relevant API groups for each 374 * package based on its installed version. 4. Converts the inner maps from "package name to list 375 * of API groups to "package name to `ApiGroupsCache`" using the `mapToApiGroupsCache` method. 376 * 5. **Constructs the result:** Builds a `PackageToApiDenyGroupsCacheMap` object from the 377 * optimized map, separating APP and SDK entries into their respective fields. 378 * 379 * @param packageToApiDenyGroupsMap The input PackageToApiDenyGroupsMap to be filtered and 380 * transformed. 381 * @param installedPackages A map of installed package names to their corresponding version 382 * codes. 383 * @return A PackageToApiDenyGroupsCacheMap containing filtered and optimized API deny group 384 * information for installed packages. 385 */ getCacheMapFilteredByInstalledPackages( PackageToApiDenyGroupsMap packageToApiDenyGroupsMap, Map<String, Long> installedPackages)386 private static PackageToApiDenyGroupsCacheMap getCacheMapFilteredByInstalledPackages( 387 PackageToApiDenyGroupsMap packageToApiDenyGroupsMap, 388 Map<String, Long> installedPackages) { 389 Map<PackageType, Map<String, List<String>>> map = 390 packageToApiDenyGroupsMap.getMapMap().entrySet().stream() 391 // filter installed packages from the file 392 .filter( 393 packageDenyApiGroupsEntry -> 394 installedPackages.containsKey( 395 packageDenyApiGroupsEntry.getKey())) 396 .collect( 397 Collectors.groupingBy( 398 entry -> entry.getValue().getPackageType(), 399 Collectors.mapping( 400 entry -> entry, 401 Collectors.toMap( 402 Map.Entry::getKey, 403 e -> 404 getApiGroupsForInstalledPackages( 405 e.getValue(), 406 installedPackages.get( 407 e.getKey())))))); 408 409 // TODO (b/365605754) add error log for unknown package type 410 return getPackageToApiDenyGroupsCacheMap(map); 411 } 412 getPackageToApiDenyGroupsCacheMap( Map<PackageType, Map<String, List<String>>> map)413 private static PackageToApiDenyGroupsCacheMap getPackageToApiDenyGroupsCacheMap( 414 Map<PackageType, Map<String, List<String>>> map) { 415 LogUtil.d("package deny map for cache is %s", map); 416 Map<PackageType, Map<String, ApiGroupsCache>> apiGroupsCacheMap = 417 map.entrySet().stream() 418 .map((e -> Map.entry(e.getKey(), mapToApiGroupsCache(e.getValue())))) 419 .filter(e -> e.getValue().size() > 0) 420 .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); 421 return PackageToApiDenyGroupsCacheMap.newBuilder() 422 .putAllAppToApiDenyGroupsCacheMap( 423 apiGroupsCacheMap.getOrDefault(PackageType.APP, Collections.emptyMap())) 424 .putAllSdkToApiDenyGroupsCacheMap( 425 apiGroupsCacheMap.getOrDefault(PackageType.SDK, Collections.emptyMap())) 426 .build(); 427 } 428 getCacheMapWithoutFilter( PackageToApiDenyGroupsMap packageToApiDenyGroupsMap)429 private static PackageToApiDenyGroupsCacheMap getCacheMapWithoutFilter( 430 PackageToApiDenyGroupsMap packageToApiDenyGroupsMap) { 431 Map<PackageType, Map<String, List<String>>> map = 432 packageToApiDenyGroupsMap.getMapMap().entrySet().stream() 433 .collect( 434 Collectors.groupingBy( 435 entry -> entry.getValue().getPackageType(), 436 Collectors.mapping( 437 entry -> entry, 438 Collectors.toMap( 439 Map.Entry::getKey, 440 e -> collectApiGroups(e.getValue()))))); 441 return getPackageToApiDenyGroupsCacheMap(map); 442 } 443 collectApiGroups(ApiDenyGroupsForPackage apiDenyGroupsForPackage)444 private static List<String> collectApiGroups(ApiDenyGroupsForPackage apiDenyGroupsForPackage) { 445 return apiDenyGroupsForPackage.getApiDenyGroupsForPackageVersionsList().stream() 446 .flatMap( 447 appApiDenyGroup -> 448 appApiDenyGroup.getApiGroups().getApiGroupList().stream()) 449 .distinct() 450 .collect(Collectors.toList()); 451 } 452 453 /** 454 * Converts a map of package names to lists of API groups into a map of package names to 455 * ApiGroupsCache objects. 456 * 457 * <p>This method iterates through the input map, filters out entries with empty API group 458 * lists, and then transforms each remaining entry into a new entry where the value is an 459 * ApiGroupsCache object built from the original list of API groups. The resulting map provides 460 * an optimized representation of API group information for each package. 461 * 462 * @param map The input map where keys are package names and values are API group names. 463 * @return A map where keys are package names and values are corresponding ApiGroupsCache 464 * objects. 465 */ mapToApiGroupsCache(Map<String, List<String>> map)466 private static Map<String, ApiGroupsCache> mapToApiGroupsCache(Map<String, List<String>> map) { 467 return map.entrySet().stream() 468 .filter(e1 -> e1.getValue().size() > 0) 469 .map( 470 e1 -> 471 Map.entry( 472 e1.getKey(), 473 ApiGroupsCache.newBuilder() 474 .addAllApiGroup(e1.getValue()) 475 .build())) 476 .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); 477 } 478 479 /** 480 * Gets a map of installed package names to their corresponding version codes. 481 * 482 * @param pm The {@link PackageManager} to use for retrieving installed applications. 483 * @return A map where the keys are package names (String) and the values are version codes 484 * (Long). 485 */ getInstalledPackageNameToVersionCodeMap(PackageManager pm)486 private Map<String, Long> getInstalledPackageNameToVersionCodeMap(PackageManager pm) { 487 Map<String, Long> installedPackages = 488 pm.getInstalledApplications(PackageManager.GET_META_DATA).stream() 489 .filter(applicationInfo -> applicationInfo.packageName != null) 490 .collect( 491 Collectors.groupingBy( 492 applicationInfo -> applicationInfo.packageName, 493 Collectors.reducing( 494 0L, // Initial value 495 applicationInfo -> 496 getPackageVersion(pm, applicationInfo), 497 // Map each application to their versioncode 498 (v1, v2) -> v1 == 0L ? v2 : v1 499 // Keep the first version code encountered 500 ))); 501 LogUtil.d("installed packages name to versioncode map is %s", installedPackages); 502 return installedPackages; 503 } 504 505 /** 506 * Retrieves a list of API groups that apply to an installed package based on its version. 507 * 508 * <p>This method iterates through the `apiDenyGroupsForPackageVersionsList` in the provided 509 * ApiDenyGroupsForPackage object. It filters the list to include only those version ranges that 510 * encompass the given `installedVersion`. For each matching version range, it extracts the 511 * associated API groups, flattens them into a single stream, removes duplicates, and finally 512 * collects them into a list. 513 * 514 * @param apiDenyGroupsForPackage The ApiDenyGroupsForPackage object containing the API deny 515 * groups and their corresponding package version ranges. 516 * @param installedVersion The installed version code of the package. 517 * @return A list of distinct API group names (String) that apply to the installed package 518 * version. 519 */ getApiGroupsForInstalledPackages( ApiDenyGroupsForPackage apiDenyGroupsForPackage, long installedVersion)520 private static List<String> getApiGroupsForInstalledPackages( 521 ApiDenyGroupsForPackage apiDenyGroupsForPackage, long installedVersion) { 522 return apiDenyGroupsForPackage.getApiDenyGroupsForPackageVersionsList().stream() 523 .filter( 524 apiDenyGroupsForAppVersions -> 525 installedVersion 526 >= apiDenyGroupsForAppVersions 527 .getPackageMinVersion() 528 && installedVersion 529 <= (apiDenyGroupsForAppVersions 530 .getPackageMaxVersion() 531 > 0 532 ? apiDenyGroupsForAppVersions 533 .getPackageMaxVersion() 534 : Long.MAX_VALUE)) 535 .flatMap( 536 appApiDenyGroup -> 537 appApiDenyGroup.getApiGroups().getApiGroupList().stream()) 538 .distinct() 539 .collect(Collectors.toList()); 540 } 541 542 /** 543 * Retrieves the version code of a package. 544 * 545 * <p>This method attempts to retrieve the long version code of a package using the provided 546 * {@link PackageManager} and {@link ApplicationInfo}. If the package is not found, it logs an 547 * error and returns 0. 548 * 549 * @param pm The {@link PackageManager} to use for retrieving package information. 550 * @param applicationInfo The {@link ApplicationInfo} containing the package name. 551 * @return The long version code of the package, or 0 if the package is not found. 552 */ getPackageVersion(PackageManager pm, ApplicationInfo applicationInfo)553 private static long getPackageVersion(PackageManager pm, ApplicationInfo applicationInfo) { 554 try { 555 return pm.getPackageInfo(applicationInfo.packageName, 0).getLongVersionCode(); 556 } catch (PackageManager.NameNotFoundException e) { 557 LogUtil.e( 558 "Deny package service cannot find package version for %s", 559 applicationInfo.packageName); 560 return 0L; 561 } 562 } 563 564 public enum PackageDenyMddProcessStatus { 565 SUCCESS(-1), 566 DISABLED(AD_SERVICES_ERROR_REPORTED__ERROR_CODE__PACKAGE_DENY_PROCESS_ERROR_DISABLED), 567 FAILURE(AD_SERVICES_ERROR_REPORTED__ERROR_CODE__PACKAGE_DENY_PROCESS_ERROR_FAILURE_UNKNOWN), 568 NO_FILE_FOUND( 569 AD_SERVICES_ERROR_REPORTED__ERROR_CODE__PACKAGE_DENY_PROCESS_ERROR_NO_FILE_FOUND), 570 FAILED_READING_FILE( 571 AD_SERVICES_ERROR_REPORTED__ERROR_CODE__PACKAGE_DENY_PROCESS_ERROR_FAILED_READING_FILE), 572 FAILED_FILTERING_INSTALLED_PACKAGES( 573 AD_SERVICES_ERROR_REPORTED__ERROR_CODE__PACKAGE_DENY_PROCESS_ERROR_FAILED_FILTERING_INSTALLED_PACKAGES), 574 FAILED_UPDATING_CACHE( 575 AD_SERVICES_ERROR_REPORTED__ERROR_CODE__PACKAGE_DENY_PROCESS_ERROR_FAILED_UPDATING_CACHE), 576 FAILED_READING_CACHE( 577 AD_SERVICES_ERROR_REPORTED__ERROR_CODE__PACKAGE_DENY_PROCESS_ERROR_FAILED_READING_CACHE), 578 ; 579 580 private final int mAdServicesErrorReportedErrorCodePackageDenyProcessError; 581 PackageDenyMddProcessStatus(int adServicesErrorReportedErrorCodePackageDenyProcessError)582 PackageDenyMddProcessStatus(int adServicesErrorReportedErrorCodePackageDenyProcessError) { 583 this.mAdServicesErrorReportedErrorCodePackageDenyProcessError = 584 adServicesErrorReportedErrorCodePackageDenyProcessError; 585 } 586 logError(PackageDenyMddProcessStatus packageDenyMddProcessStatus)587 static void logError(PackageDenyMddProcessStatus packageDenyMddProcessStatus) { 588 ErrorLogUtil.e( 589 packageDenyMddProcessStatus 590 .mAdServicesErrorReportedErrorCodePackageDenyProcessError, 591 AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__COMMON); 592 } 593 } 594 595 private static final class PackageDenyException extends RuntimeException { PackageDenyException(PackageDenyMddProcessStatus packageDenyMddProcessStatus)596 PackageDenyException(PackageDenyMddProcessStatus packageDenyMddProcessStatus) { 597 super(packageDenyMddProcessStatus.name()); 598 } 599 PackageDenyException(PackageDenyMddProcessStatus packageDenyMddProcessStatus, Throwable t)600 PackageDenyException(PackageDenyMddProcessStatus packageDenyMddProcessStatus, Throwable t) { 601 super(packageDenyMddProcessStatus.name(), t); 602 } 603 } 604 } 605