1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ondevicepersonalization.services.manifest; 18 19 import static android.content.pm.PackageManager.GET_META_DATA; 20 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.pm.PackageInfo; 24 import android.content.pm.PackageManager; 25 import android.content.res.Resources; 26 import android.content.res.XmlResourceParser; 27 28 import com.android.ondevicepersonalization.internal.util.LoggerFactory; 29 import com.android.ondevicepersonalization.services.enrollment.PartnerEnrollmentChecker; 30 31 import com.google.common.collect.ImmutableList; 32 33 /** 34 * Helper class for parsing and checking app manifest configs 35 */ 36 public final class AppManifestConfigHelper { 37 private static final String ON_DEVICE_PERSONALIZATION_CONFIG_PROPERTY = 38 "android.ondevicepersonalization.ON_DEVICE_PERSONALIZATION_CONFIG"; 39 private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger(); 40 private static final String TAG = AppManifestConfigHelper.class.getSimpleName(); 41 AppManifestConfigHelper()42 private AppManifestConfigHelper() { 43 } 44 45 /** 46 * Determines if the given package's manifest contains ODP settings 47 * 48 * @param context the context of the API call. 49 * @param packageName the packageName of the package whose manifest config will be read 50 * @return true if the ODP setting exists, false otherwise 51 */ manifestContainsOdpSettings(Context context, String packageName)52 public static boolean manifestContainsOdpSettings(Context context, String packageName) { 53 PackageManager pm = context.getPackageManager(); 54 try { 55 pm.getProperty(ON_DEVICE_PERSONALIZATION_CONFIG_PROPERTY, packageName); 56 } catch (PackageManager.NameNotFoundException e) { 57 return false; 58 } 59 return true; 60 } 61 62 /** 63 * Returns the ODP manifest config for a package. 64 * 65 * <p>Throws a {@link RuntimeException} if the package, its ODP settings are not found or cannot 66 * be parsed. 67 */ 68 /** Returns the ODP manifest config for a package. */ getAppManifestConfig(Context context, String packageName)69 public static AppManifestConfig getAppManifestConfig(Context context, String packageName) { 70 if (!manifestContainsOdpSettings(context, packageName)) { 71 // TODO(b/241941021) Determine correct exception to throw 72 throw new IllegalArgumentException( 73 "OdpSettings not found for package: " + packageName.toString()); 74 } 75 PackageManager pm = context.getPackageManager(); 76 try { 77 int resId = pm.getProperty(ON_DEVICE_PERSONALIZATION_CONFIG_PROPERTY, 78 packageName).getResourceId(); 79 Resources resources = pm.getResourcesForApplication(packageName); 80 XmlResourceParser xmlParser = resources.getXml(resId); 81 // TODO(b/239479120) Update to avoid re-parsing the XML too frequently if required 82 return AppManifestConfigParser.getConfig(xmlParser); 83 } catch (Exception e) { 84 // TODO(b/241941021) Determine correct exception to throw 85 throw new IllegalArgumentException( 86 "Failed to parse manifest for package: " + packageName, e); 87 } 88 } 89 90 /** 91 * Gets the download URL from package's ODP settings config 92 * 93 * @param context the context of the API call. 94 * @param packageName the packageName of the package whose manifest config will be read 95 */ getDownloadUrlFromOdpSettings(Context context, String packageName)96 public static String getDownloadUrlFromOdpSettings(Context context, String packageName) { 97 return getAppManifestConfig(context, packageName).getDownloadUrl(); 98 } 99 100 /** 101 * Gets the service name from package's ODP settings config 102 * 103 * @param context the context of the API call. 104 * @param packageName the packageName of the package whose manifest config will be read 105 */ getServiceNameFromOdpSettings(Context context, String packageName)106 public static String getServiceNameFromOdpSettings(Context context, 107 String packageName) { 108 return getAppManifestConfig(context, packageName).getServiceName(); 109 } 110 111 /** 112 * Gets the federated compute service remote server url from package's ODP settings config 113 * 114 * @param context the context of the API call. 115 * @param packageName the packageName of the package whose manifest config will be read 116 */ getFcRemoteServerUrlFromOdpSettings(Context context, String packageName)117 public static String getFcRemoteServerUrlFromOdpSettings(Context context, 118 String packageName) { 119 return getAppManifestConfig(context, packageName).getFcRemoteServerUrl(); 120 } 121 122 /** 123 * Get the list of packages enrolled for ODP, by checking manifest for ODP settings. 124 * 125 * @param context The context of the calling process. 126 * @param enrolledOnly Whether to only include packages that pass the enrollment check. 127 * @return The list of packages that contain ODP manifest settings (and potentially enrolled) 128 */ getOdpPackages(Context context, boolean enrolledOnly)129 public static ImmutableList<String> getOdpPackages(Context context, boolean enrolledOnly) { 130 ImmutableList.Builder<String> builder = new ImmutableList.Builder<>(); 131 for (PackageInfo packageInfo : 132 context.getPackageManager() 133 .getInstalledPackages(PackageManager.PackageInfoFlags.of(GET_META_DATA))) { 134 String packageName = packageInfo.packageName; 135 136 if (manifestContainsOdpSettings(context, packageName)) { 137 if (enrolledOnly 138 && !PartnerEnrollmentChecker.isIsolatedServiceEnrolled(packageName)) { 139 sLogger.d(TAG + ": package %s has ODP manifest, but not enrolled", packageName); 140 continue; 141 } 142 143 String enrolledString = enrolledOnly ? "and is enrolled" : ""; 144 sLogger.d(TAG + ": package %s has ODP manifest " + enrolledString, packageName); 145 builder.add(packageName); 146 } 147 } 148 return builder.build(); 149 } 150 151 /** 152 * Get the list of services enrolled for ODP. 153 * 154 * @param context The context of the calling process. 155 * @param enrolledOnly Whether to only include services that pass the enrollment check. 156 * @return The list of matching Services with ODP manifest settings (and are potentially 157 * enrolled). 158 */ getOdpServices( Context context, boolean enrolledOnly)159 public static ImmutableList<ComponentName> getOdpServices( 160 Context context, boolean enrolledOnly) { 161 ImmutableList.Builder<ComponentName> builder = new ImmutableList.Builder<>(); 162 for (PackageInfo packageInfo : 163 context.getPackageManager() 164 .getInstalledPackages(PackageManager.PackageInfoFlags.of(GET_META_DATA))) { 165 String packageName = packageInfo.packageName; 166 167 if (manifestContainsOdpSettings(context, packageName)) { 168 if (enrolledOnly 169 && !PartnerEnrollmentChecker.isIsolatedServiceEnrolled(packageName)) { 170 sLogger.d(TAG + ": service %s has ODP manifest, but not enrolled", packageName); 171 continue; 172 } 173 174 String enrolledString = enrolledOnly ? "and is enrolled" : ""; 175 sLogger.d(TAG + ": service %s has ODP manifest " + enrolledString, packageName); 176 177 String serviceClass = getServiceNameFromOdpSettings(context, packageName); 178 ComponentName service = ComponentName.createRelative(packageName, serviceClass); 179 builder.add(service); 180 } 181 } 182 return builder.build(); 183 } 184 } 185