• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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