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.download.mdd; 18 19 import static android.content.pm.PackageManager.GET_META_DATA; 20 21 import android.content.Context; 22 import android.content.pm.PackageInfo; 23 import android.content.pm.PackageManager; 24 import android.net.Uri; 25 import android.util.Log; 26 27 import com.android.internal.annotations.VisibleForTesting; 28 import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors; 29 import com.android.ondevicepersonalization.services.data.vendor.OnDevicePersonalizationVendorDataDao; 30 import com.android.ondevicepersonalization.services.manifest.AppManifestConfigHelper; 31 import com.android.ondevicepersonalization.services.util.PackageUtils; 32 33 import com.google.android.libraries.mobiledatadownload.AddFileGroupRequest; 34 import com.google.android.libraries.mobiledatadownload.FileGroupPopulator; 35 import com.google.android.libraries.mobiledatadownload.GetFileGroupsByFilterRequest; 36 import com.google.android.libraries.mobiledatadownload.MobileDataDownload; 37 import com.google.android.libraries.mobiledatadownload.RemoveFileGroupRequest; 38 import com.google.android.libraries.mobiledatadownload.tracing.PropagatedFutures; 39 import com.google.common.util.concurrent.FluentFuture; 40 import com.google.common.util.concurrent.Futures; 41 import com.google.common.util.concurrent.ListenableFuture; 42 import com.google.mobiledatadownload.ClientConfigProto; 43 import com.google.mobiledatadownload.DownloadConfigProto.DataFile; 44 import com.google.mobiledatadownload.DownloadConfigProto.DataFile.ChecksumType; 45 import com.google.mobiledatadownload.DownloadConfigProto.DataFileGroup; 46 import com.google.mobiledatadownload.DownloadConfigProto.DownloadConditions; 47 import com.google.mobiledatadownload.DownloadConfigProto.DownloadConditions.DeviceNetworkPolicy; 48 49 import java.util.ArrayList; 50 import java.util.HashSet; 51 import java.util.List; 52 import java.util.Set; 53 54 /** 55 * FileGroupPopulator to add FileGroups for ODP onboarded packages 56 */ 57 public class OnDevicePersonalizationFileGroupPopulator implements FileGroupPopulator { 58 private static final String TAG = "OnDevicePersonalizationFileGroupPopulator"; 59 60 private final Context mContext; 61 OnDevicePersonalizationFileGroupPopulator(Context context)62 public OnDevicePersonalizationFileGroupPopulator(Context context) { 63 this.mContext = context; 64 } 65 66 /** 67 * A helper function to create a DataFilegroup. 68 */ createDataFileGroup( String groupName, String ownerPackage, String[] fileId, int[] byteSize, String[] checksum, ChecksumType[] checksumType, String[] url, DeviceNetworkPolicy deviceNetworkPolicy)69 public static DataFileGroup createDataFileGroup( 70 String groupName, 71 String ownerPackage, 72 String[] fileId, 73 int[] byteSize, 74 String[] checksum, 75 ChecksumType[] checksumType, 76 String[] url, 77 DeviceNetworkPolicy deviceNetworkPolicy) { 78 if (fileId.length != byteSize.length 79 || fileId.length != checksum.length 80 || fileId.length != url.length 81 || checksumType.length != fileId.length) { 82 throw new IllegalArgumentException(); 83 } 84 85 DataFileGroup.Builder dataFileGroupBuilder = 86 DataFileGroup.newBuilder() 87 .setGroupName(groupName) 88 .setOwnerPackage(ownerPackage) 89 .setDownloadConditions( 90 DownloadConditions.newBuilder().setDeviceNetworkPolicy( 91 deviceNetworkPolicy)); 92 93 for (int i = 0; i < fileId.length; ++i) { 94 DataFile file = 95 DataFile.newBuilder() 96 .setFileId(fileId[i]) 97 .setByteSize(byteSize[i]) 98 .setChecksum(checksum[i]) 99 .setChecksumType(checksumType[i]) 100 .setUrlToDownload(url[i]) 101 .build(); 102 dataFileGroupBuilder.addFile(file); 103 } 104 105 return dataFileGroupBuilder.build(); 106 } 107 108 /** 109 * Creates the fileGroup name based off the package's name and cert. 110 * 111 * @param packageName Name of the package owning the fileGroup 112 * @param context Context of the calling service/application 113 * @return The created fileGroup name. 114 */ createPackageFileGroupName(String packageName, Context context)115 public static String createPackageFileGroupName(String packageName, Context context) throws 116 PackageManager.NameNotFoundException { 117 return packageName + "_" + PackageUtils.getCertDigest(context, packageName); 118 } 119 120 /** 121 * Creates the MDD download URL for the given package 122 * 123 * @param packageName PackageName of the package owning the fileGroup 124 * @param context Context of the calling service/application 125 * @return The created MDD URL for the package. 126 */ 127 @VisibleForTesting createDownloadUrl(String packageName, Context context)128 public static String createDownloadUrl(String packageName, Context context) throws 129 PackageManager.NameNotFoundException { 130 String baseURL = AppManifestConfigHelper.getDownloadUrlFromOdpSettings( 131 context, packageName); 132 if (baseURL == null) { 133 throw new IllegalArgumentException("Failed to retrieve base download URL"); 134 } 135 Uri uri = Uri.parse(baseURL); 136 137 // Enforce URI scheme 138 if (OnDevicePersonalizationLocalFileDownloader.isLocalOdpUri(uri)) { 139 if (!PackageUtils.isPackageDebuggable(context, packageName)) { 140 throw new IllegalArgumentException("Local urls are only valid " 141 + "for debuggable packages: " + baseURL); 142 } 143 } else if (!baseURL.startsWith("https")) { 144 throw new IllegalArgumentException("File url is not secure: " + baseURL); 145 } 146 147 return addDownloadUrlQueryParameters(uri, packageName, context); 148 } 149 150 /** 151 * Adds query parameters to the download URL. 152 */ addDownloadUrlQueryParameters(Uri uri, String packageName, Context context)153 private static String addDownloadUrlQueryParameters(Uri uri, String packageName, 154 Context context) 155 throws PackageManager.NameNotFoundException { 156 long syncToken = OnDevicePersonalizationVendorDataDao.getInstance(context, packageName, 157 PackageUtils.getCertDigest(context, packageName)).getSyncToken(); 158 if (syncToken != -1) { 159 uri = uri.buildUpon().appendQueryParameter("syncToken", 160 String.valueOf(syncToken)).build(); 161 } 162 // TODO(b/267177135) Add user data query parameters here 163 return uri.toString(); 164 } 165 166 @Override refreshFileGroups(MobileDataDownload mobileDataDownload)167 public ListenableFuture<Void> refreshFileGroups(MobileDataDownload mobileDataDownload) { 168 GetFileGroupsByFilterRequest request = 169 GetFileGroupsByFilterRequest.newBuilder().setIncludeAllGroups(true).build(); 170 return FluentFuture.from(mobileDataDownload.getFileGroupsByFilter(request)) 171 .transformAsync(fileGroupList -> { 172 Set<String> fileGroupsToRemove = new HashSet<>(); 173 for (ClientConfigProto.ClientFileGroup fileGroup : fileGroupList) { 174 fileGroupsToRemove.add(fileGroup.getGroupName()); 175 } 176 List<ListenableFuture<Boolean>> mFutures = new ArrayList<>(); 177 for (PackageInfo packageInfo : mContext.getPackageManager() 178 .getInstalledPackages( 179 PackageManager.PackageInfoFlags.of(GET_META_DATA))) { 180 if (AppManifestConfigHelper.manifestContainsOdpSettings( 181 mContext, packageInfo.packageName)) { 182 try { 183 String groupName = createPackageFileGroupName( 184 packageInfo.packageName, 185 mContext); 186 fileGroupsToRemove.remove(groupName); 187 String ownerPackage = mContext.getPackageName(); 188 String fileId = groupName; 189 int byteSize = 0; 190 String checksum = ""; 191 ChecksumType checksumType = ChecksumType.NONE; 192 String downloadUrl = createDownloadUrl(packageInfo.packageName, 193 mContext); 194 DeviceNetworkPolicy deviceNetworkPolicy = 195 DeviceNetworkPolicy.DOWNLOAD_ONLY_ON_WIFI; 196 DataFileGroup dataFileGroup = createDataFileGroup( 197 groupName, 198 ownerPackage, 199 new String[]{fileId}, 200 new int[]{byteSize}, 201 new String[]{checksum}, 202 new ChecksumType[]{checksumType}, 203 new String[]{downloadUrl}, 204 deviceNetworkPolicy); 205 mFutures.add(mobileDataDownload.addFileGroup( 206 AddFileGroupRequest.newBuilder().setDataFileGroup( 207 dataFileGroup).build())); 208 } catch (Exception e) { 209 Log.e(TAG, "Failed to create file group for " 210 + packageInfo.packageName, e); 211 } 212 } 213 } 214 215 for (String group : fileGroupsToRemove) { 216 mFutures.add(mobileDataDownload.removeFileGroup( 217 RemoveFileGroupRequest.newBuilder().setGroupName(group).build())); 218 } 219 220 return PropagatedFutures.transform( 221 Futures.successfulAsList(mFutures), 222 result -> { 223 if (result.contains(null)) { 224 Log.d(TAG, "Failed to add or remove a file group"); 225 } else { 226 Log.d(TAG, "Successfully updated all file groups"); 227 } 228 return null; 229 }, 230 OnDevicePersonalizationExecutors.getBackgroundExecutor() 231 ); 232 }, OnDevicePersonalizationExecutors.getBackgroundExecutor()); 233 } 234 } 235