blob: 822b42132527b355384eb577288250b66e59909d [file] [log] [blame]
/*
* Copyright 2022 Google LLC
*
* 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.google.android.libraries.mobiledatadownload.internal.util;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.VisibleForTesting;
import com.google.android.libraries.mobiledatadownload.SilentFeedback;
import com.google.android.libraries.mobiledatadownload.file.backends.AndroidUri;
import com.google.android.libraries.mobiledatadownload.file.backends.BlobUri;
import com.google.android.libraries.mobiledatadownload.internal.logging.LogUtil;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal.AllowedReaders;
import java.io.IOException;
import javax.annotation.Nullable;
/** Utils to help with directory manipulation. */
public class DirectoryUtil {
private static final String TAG = "DirectoryUtil";
// Correspond to MobStore Uri components.
public static final String MDD_STORAGE_MODULE = "datadownload";
public static final String MDD_MANIFEST_MODULE = "datadownloadmanifest";
public static final String MDD_STORAGE_ALL_GOOGLE_APPS = "public";
public static final String MDD_STORAGE_ONLY_GOOGLE_PLAY_SERVICES = "private";
public static final String MDD_STORAGE_SYMLINKS = "links";
@VisibleForTesting static final String MDD_STORAGE_ALL_APPS = "public_3p";
/**
* Returns the top-level directory uri for all MDD downloads. Individual files should not be
* placed here; instead, use {@link #getDownloadDirectory(Context, AllowedReaders)}.
*/
public static Uri getBaseDownloadDirectory(Context context, Optional<String> instanceId) {
AndroidUri.Builder builder =
AndroidUri.builder(context)
.setModule(
instanceId != null && instanceId.isPresent()
? instanceId.get()
: MDD_STORAGE_MODULE);
if (instanceId != null && instanceId.isPresent()) {
builder.setRelativePath(MDD_STORAGE_MODULE);
}
return builder.build();
}
/**
* Returns the base directory where MDD stores manifest files. If instanceId is absent, a shared
* directory is returned; otherwise, a standalone directory with instanceId as its relative path
* is returned.
*/
public static Uri getManifestDirectory(Context context, Optional<String> instanceId) {
Preconditions.checkNotNull(instanceId);
return AndroidUri.builder(context)
.setModule(MDD_MANIFEST_MODULE)
.setRelativePath(instanceId.or(MDD_STORAGE_MODULE))
.build();
}
/** Returns the directory uri for mdd download based on the allowed readers. */
public static Uri getDownloadDirectory(
Context context, AllowedReaders allowedReaders, Optional<String> instanceId) {
String subDirectory = getSubDirectory(allowedReaders);
return getBaseDownloadDirectory(context, instanceId)
.buildUpon()
.appendPath(subDirectory)
.build();
}
/** Returns the directory uri base for mdd symlinks. */
public static Uri getBaseDownloadSymlinkDirectory(Context context, Optional<String> instanceId) {
return getBaseDownloadDirectory(context, instanceId)
.buildUpon()
.appendPath(MDD_STORAGE_SYMLINKS)
.build();
}
/** Returns the directory uri for mdd symlinks based on the allowed readers. */
public static Uri getDownloadSymlinkDirectory(
Context context, AllowedReaders allowedReaders, Optional<String> instanceId) {
String subDirectory = getSubDirectory(allowedReaders);
return getBaseDownloadSymlinkDirectory(context, instanceId)
.buildUpon()
.appendPath(subDirectory)
.build();
}
/**
* Returns the on device uri for the specified file.
*
* @param androidShared if sets to true, {@code getOnDeviceUri} returns the "blobstore" scheme
* URI, otherwise it returns the "android" scheme URI.
*/
// TODO(b/118137672): getOnDeviceUri shouldn't return null on error.
@Nullable
public static Uri getOnDeviceUri(
Context context,
AllowedReaders allowedReaders,
String fileName,
String checksum,
SilentFeedback silentFeedback,
Optional<String> instanceId,
boolean androidShared) {
try {
if (androidShared) {
return getBlobUri(context, checksum);
}
Uri directoryUri = getDownloadDirectory(context, allowedReaders, instanceId);
return directoryUri.buildUpon().appendPath(fileName).build();
} catch (Exception e) {
// Catch all exceptions here as the above code can throw an exception if
// context.getFilesDir returns null.
LogUtil.e(e, "%s: Unable to create mobstore uri for file %s.", TAG, fileName);
silentFeedback.send(e, "Unable to create mobstore uri for file");
return null;
}
}
/**
* Returns the "blobstore" scheme URI of the file with final checksum {@code checksum}.
*
* <ul>
* In order to be able to access the file in the blob store, the checksum needs to comply to the
* following rules:
* <li>at the moment, only checksums of type SHA256 are accepted.
* <li>the checksum must be the file final checksum, i.e. after the download transforms have
* been applied if any.
* </ul>
*/
public static Uri getBlobUri(Context context, String checksum) throws IOException {
return BlobUri.builder(context).setBlobParameters(checksum).build();
}
/**
* Returns the "blobstore" scheme URI used to acquire a lease on the file with final checksum
* {@code checksum}.
*
* <ul>
* In order to be able to acquire the lease of the file in the blob store, the checksum needs to
* comply to the following rules:
* <li>at the moment, only checksums of type SHA256 are accepted.
* <li>the checksum must be the file final checksum, i.e. for files with download_transform, it
* should contain the transform of the file after the transforms have been applied.
* </ul>
*/
public static Uri getBlobStoreLeaseUri(Context context, String checksum, long expiryDateSecs)
throws IOException {
return BlobUri.builder(context).setLeaseParameters(checksum, expiryDateSecs).build();
}
/**
* Returns the "blobstore" scheme URI used to release all the leases owned by the calling package.
*/
public static Uri getBlobStoreAllLeasesUri(Context context) throws IOException {
return BlobUri.builder(context).setAllLeasesParameters().build();
}
/**
* Returns {@code basename.extension}, with {@code instanceId} appended to basename if present.
*
* <p>Useful for building filenames that must be distinguished by InstanceId while keeping the
* same basename and file extension.
*/
public static String buildFilename(
String basename, String extension, Optional<String> instanceId) {
String resultBasename = basename;
if (instanceId != null && instanceId.isPresent()) {
resultBasename += instanceId.get();
}
return resultBasename + "." + extension;
}
/** Convenience method to get the storage subdirectory based on the allowed readers. */
private static String getSubDirectory(AllowedReaders allowedReaders) {
switch (allowedReaders) {
case ALL_GOOGLE_APPS:
return MDD_STORAGE_ALL_GOOGLE_APPS;
case ONLY_GOOGLE_PLAY_SERVICES:
return MDD_STORAGE_ONLY_GOOGLE_PLAY_SERVICES;
case ALL_APPS:
return MDD_STORAGE_ALL_APPS;
}
throw new IllegalArgumentException("invalid allowed readers value");
}
}