blob: e4eec7c71f6209bc4bb28bd47bd507bf61722979 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ondevicepersonalization.services.download.mdd;
import static android.content.pm.PackageManager.GET_META_DATA;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
import com.android.ondevicepersonalization.services.data.vendor.OnDevicePersonalizationVendorDataDao;
import com.android.ondevicepersonalization.services.manifest.AppManifestConfigHelper;
import com.android.ondevicepersonalization.services.util.PackageUtils;
import com.google.android.libraries.mobiledatadownload.AddFileGroupRequest;
import com.google.android.libraries.mobiledatadownload.FileGroupPopulator;
import com.google.android.libraries.mobiledatadownload.GetFileGroupsByFilterRequest;
import com.google.android.libraries.mobiledatadownload.MobileDataDownload;
import com.google.android.libraries.mobiledatadownload.RemoveFileGroupRequest;
import com.google.android.libraries.mobiledatadownload.tracing.PropagatedFutures;
import com.google.common.util.concurrent.FluentFuture;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.mobiledatadownload.ClientConfigProto;
import com.google.mobiledatadownload.DownloadConfigProto.DataFile;
import com.google.mobiledatadownload.DownloadConfigProto.DataFile.ChecksumType;
import com.google.mobiledatadownload.DownloadConfigProto.DataFileGroup;
import com.google.mobiledatadownload.DownloadConfigProto.DownloadConditions;
import com.google.mobiledatadownload.DownloadConfigProto.DownloadConditions.DeviceNetworkPolicy;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* FileGroupPopulator to add FileGroups for ODP onboarded packages
*/
public class OnDevicePersonalizationFileGroupPopulator implements FileGroupPopulator {
private static final String TAG = "OnDevicePersonalizationFileGroupPopulator";
private final Context mContext;
public OnDevicePersonalizationFileGroupPopulator(Context context) {
this.mContext = context;
}
/**
* A helper function to create a DataFilegroup.
*/
public static DataFileGroup createDataFileGroup(
String groupName,
String ownerPackage,
String[] fileId,
int[] byteSize,
String[] checksum,
ChecksumType[] checksumType,
String[] url,
DeviceNetworkPolicy deviceNetworkPolicy) {
if (fileId.length != byteSize.length
|| fileId.length != checksum.length
|| fileId.length != url.length
|| checksumType.length != fileId.length) {
throw new IllegalArgumentException();
}
DataFileGroup.Builder dataFileGroupBuilder =
DataFileGroup.newBuilder()
.setGroupName(groupName)
.setOwnerPackage(ownerPackage)
.setDownloadConditions(
DownloadConditions.newBuilder().setDeviceNetworkPolicy(
deviceNetworkPolicy));
for (int i = 0; i < fileId.length; ++i) {
DataFile file =
DataFile.newBuilder()
.setFileId(fileId[i])
.setByteSize(byteSize[i])
.setChecksum(checksum[i])
.setChecksumType(checksumType[i])
.setUrlToDownload(url[i])
.build();
dataFileGroupBuilder.addFile(file);
}
return dataFileGroupBuilder.build();
}
/**
* Creates the fileGroup name based off the package's name and cert.
*
* @param packageName Name of the package owning the fileGroup
* @param context Context of the calling service/application
* @return The created fileGroup name.
*/
public static String createPackageFileGroupName(String packageName, Context context) throws
PackageManager.NameNotFoundException {
return packageName + "_" + PackageUtils.getCertDigest(context, packageName);
}
/**
* Creates the MDD download URL for the given package
*
* @param packageName PackageName of the package owning the fileGroup
* @param context Context of the calling service/application
* @return The created MDD URL for the package.
*/
@VisibleForTesting
public static String createDownloadUrl(String packageName, Context context) throws
PackageManager.NameNotFoundException {
String baseURL = AppManifestConfigHelper.getDownloadUrlFromOdpSettings(
context, packageName);
if (baseURL == null) {
throw new IllegalArgumentException("Failed to retrieve base download URL");
}
Uri uri = Uri.parse(baseURL);
// Enforce URI scheme
if (OnDevicePersonalizationLocalFileDownloader.isLocalOdpUri(uri)) {
if (!PackageUtils.isPackageDebuggable(context, packageName)) {
throw new IllegalArgumentException("Local urls are only valid "
+ "for debuggable packages: " + baseURL);
}
} else if (!baseURL.startsWith("https")) {
throw new IllegalArgumentException("File url is not secure: " + baseURL);
}
return addDownloadUrlQueryParameters(uri, packageName, context);
}
/**
* Adds query parameters to the download URL.
*/
private static String addDownloadUrlQueryParameters(Uri uri, String packageName,
Context context)
throws PackageManager.NameNotFoundException {
long syncToken = OnDevicePersonalizationVendorDataDao.getInstance(context, packageName,
PackageUtils.getCertDigest(context, packageName)).getSyncToken();
if (syncToken != -1) {
uri = uri.buildUpon().appendQueryParameter("syncToken",
String.valueOf(syncToken)).build();
}
// TODO(b/267177135) Add user data query parameters here
return uri.toString();
}
@Override
public ListenableFuture<Void> refreshFileGroups(MobileDataDownload mobileDataDownload) {
GetFileGroupsByFilterRequest request =
GetFileGroupsByFilterRequest.newBuilder().setIncludeAllGroups(true).build();
return FluentFuture.from(mobileDataDownload.getFileGroupsByFilter(request))
.transformAsync(fileGroupList -> {
Set<String> fileGroupsToRemove = new HashSet<>();
for (ClientConfigProto.ClientFileGroup fileGroup : fileGroupList) {
fileGroupsToRemove.add(fileGroup.getGroupName());
}
List<ListenableFuture<Boolean>> mFutures = new ArrayList<>();
for (PackageInfo packageInfo : mContext.getPackageManager()
.getInstalledPackages(
PackageManager.PackageInfoFlags.of(GET_META_DATA))) {
if (AppManifestConfigHelper.manifestContainsOdpSettings(
mContext, packageInfo.packageName)) {
try {
String groupName = createPackageFileGroupName(
packageInfo.packageName,
mContext);
fileGroupsToRemove.remove(groupName);
String ownerPackage = mContext.getPackageName();
String fileId = groupName;
int byteSize = 0;
String checksum = "";
ChecksumType checksumType = ChecksumType.NONE;
String downloadUrl = createDownloadUrl(packageInfo.packageName,
mContext);
DeviceNetworkPolicy deviceNetworkPolicy =
DeviceNetworkPolicy.DOWNLOAD_ONLY_ON_WIFI;
DataFileGroup dataFileGroup = createDataFileGroup(
groupName,
ownerPackage,
new String[]{fileId},
new int[]{byteSize},
new String[]{checksum},
new ChecksumType[]{checksumType},
new String[]{downloadUrl},
deviceNetworkPolicy);
mFutures.add(mobileDataDownload.addFileGroup(
AddFileGroupRequest.newBuilder().setDataFileGroup(
dataFileGroup).build()));
} catch (Exception e) {
Log.e(TAG, "Failed to create file group for "
+ packageInfo.packageName, e);
}
}
}
for (String group : fileGroupsToRemove) {
mFutures.add(mobileDataDownload.removeFileGroup(
RemoveFileGroupRequest.newBuilder().setGroupName(group).build()));
}
return PropagatedFutures.transform(
Futures.successfulAsList(mFutures),
result -> {
if (result.contains(null)) {
Log.d(TAG, "Failed to add or remove a file group");
} else {
Log.d(TAG, "Successfully updated all file groups");
}
return null;
},
OnDevicePersonalizationExecutors.getBackgroundExecutor()
);
}, OnDevicePersonalizationExecutors.getBackgroundExecutor());
}
}