1 /* 2 * Copyright 2022 Google LLC 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 package com.google.android.libraries.mobiledatadownload.internal.util; 17 18 import android.content.Context; 19 import android.net.Uri; 20 import androidx.annotation.VisibleForTesting; 21 import com.google.android.libraries.mobiledatadownload.SilentFeedback; 22 import com.google.android.libraries.mobiledatadownload.file.backends.AndroidUri; 23 import com.google.android.libraries.mobiledatadownload.file.backends.BlobUri; 24 import com.google.android.libraries.mobiledatadownload.internal.logging.LogUtil; 25 import com.google.common.base.Optional; 26 import com.google.common.base.Preconditions; 27 import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal.AllowedReaders; 28 import java.io.IOException; 29 import javax.annotation.Nullable; 30 31 /** Utils to help with directory manipulation. */ 32 public class DirectoryUtil { 33 34 private static final String TAG = "DirectoryUtil"; 35 // Correspond to MobStore Uri components. 36 public static final String MDD_STORAGE_MODULE = "datadownload"; 37 public static final String MDD_MANIFEST_MODULE = "datadownloadmanifest"; 38 public static final String MDD_STORAGE_ALL_GOOGLE_APPS = "public"; 39 public static final String MDD_STORAGE_ONLY_GOOGLE_PLAY_SERVICES = "private"; 40 public static final String MDD_STORAGE_SYMLINKS = "links"; 41 @VisibleForTesting static final String MDD_STORAGE_ALL_APPS = "public_3p"; 42 43 /** 44 * Returns the top-level directory uri for all MDD downloads. Individual files should not be 45 * placed here; instead, use {@link #getDownloadDirectory(Context, AllowedReaders)}. 46 */ getBaseDownloadDirectory(Context context, Optional<String> instanceId)47 public static Uri getBaseDownloadDirectory(Context context, Optional<String> instanceId) { 48 AndroidUri.Builder builder = 49 AndroidUri.builder(context) 50 .setModule( 51 instanceId != null && instanceId.isPresent() 52 ? instanceId.get() 53 : MDD_STORAGE_MODULE); 54 55 if (instanceId != null && instanceId.isPresent()) { 56 builder.setRelativePath(MDD_STORAGE_MODULE); 57 } 58 59 return builder.build(); 60 } 61 62 /** 63 * Returns the base directory where MDD stores manifest files. If instanceId is absent, a shared 64 * directory is returned; otherwise, a standalone directory with instanceId as its relative path 65 * is returned. 66 */ getManifestDirectory(Context context, Optional<String> instanceId)67 public static Uri getManifestDirectory(Context context, Optional<String> instanceId) { 68 Preconditions.checkNotNull(instanceId); 69 70 return AndroidUri.builder(context) 71 .setModule(MDD_MANIFEST_MODULE) 72 .setRelativePath(instanceId.or(MDD_STORAGE_MODULE)) 73 .build(); 74 } 75 76 /** Returns the directory uri for mdd download based on the allowed readers. */ getDownloadDirectory( Context context, AllowedReaders allowedReaders, Optional<String> instanceId)77 public static Uri getDownloadDirectory( 78 Context context, AllowedReaders allowedReaders, Optional<String> instanceId) { 79 String subDirectory = getSubDirectory(allowedReaders); 80 return getBaseDownloadDirectory(context, instanceId) 81 .buildUpon() 82 .appendPath(subDirectory) 83 .build(); 84 } 85 86 /** Returns the directory uri base for mdd symlinks. */ getBaseDownloadSymlinkDirectory(Context context, Optional<String> instanceId)87 public static Uri getBaseDownloadSymlinkDirectory(Context context, Optional<String> instanceId) { 88 return getBaseDownloadDirectory(context, instanceId) 89 .buildUpon() 90 .appendPath(MDD_STORAGE_SYMLINKS) 91 .build(); 92 } 93 94 /** Returns the directory uri for mdd symlinks based on the allowed readers. */ getDownloadSymlinkDirectory( Context context, AllowedReaders allowedReaders, Optional<String> instanceId)95 public static Uri getDownloadSymlinkDirectory( 96 Context context, AllowedReaders allowedReaders, Optional<String> instanceId) { 97 String subDirectory = getSubDirectory(allowedReaders); 98 return getBaseDownloadSymlinkDirectory(context, instanceId) 99 .buildUpon() 100 .appendPath(subDirectory) 101 .build(); 102 } 103 104 /** 105 * Returns the on device uri for the specified file. 106 * 107 * @param androidShared if sets to true, {@code getOnDeviceUri} returns the "blobstore" scheme 108 * URI, otherwise it returns the "android" scheme URI. 109 */ 110 // TODO(b/118137672): getOnDeviceUri shouldn't return null on error. 111 112 @Nullable getOnDeviceUri( Context context, AllowedReaders allowedReaders, String fileName, String checksum, SilentFeedback silentFeedback, Optional<String> instanceId, boolean androidShared)113 public static Uri getOnDeviceUri( 114 Context context, 115 AllowedReaders allowedReaders, 116 String fileName, 117 String checksum, 118 SilentFeedback silentFeedback, 119 Optional<String> instanceId, 120 boolean androidShared) { 121 122 try { 123 if (androidShared) { 124 return getBlobUri(context, checksum); 125 } 126 Uri directoryUri = getDownloadDirectory(context, allowedReaders, instanceId); 127 return directoryUri.buildUpon().appendPath(fileName).build(); 128 } catch (Exception e) { 129 // Catch all exceptions here as the above code can throw an exception if 130 // context.getFilesDir returns null. 131 LogUtil.e(e, "%s: Unable to create mobstore uri for file %s.", TAG, fileName); 132 silentFeedback.send(e, "Unable to create mobstore uri for file"); 133 134 return null; 135 } 136 } 137 138 /** 139 * Returns the "blobstore" scheme URI of the file with final checksum {@code checksum}. 140 * 141 * <ul> 142 * In order to be able to access the file in the blob store, the checksum needs to comply to the 143 * following rules: 144 * <li>at the moment, only checksums of type SHA256 are accepted. 145 * <li>the checksum must be the file final checksum, i.e. after the download transforms have 146 * been applied if any. 147 * </ul> 148 */ getBlobUri(Context context, String checksum)149 public static Uri getBlobUri(Context context, String checksum) throws IOException { 150 return BlobUri.builder(context).setBlobParameters(checksum).build(); 151 } 152 153 /** 154 * Returns the "blobstore" scheme URI used to acquire a lease on the file with final checksum 155 * {@code checksum}. 156 * 157 * <ul> 158 * In order to be able to acquire the lease of the file in the blob store, the checksum needs to 159 * comply to the following rules: 160 * <li>at the moment, only checksums of type SHA256 are accepted. 161 * <li>the checksum must be the file final checksum, i.e. for files with download_transform, it 162 * should contain the transform of the file after the transforms have been applied. 163 * </ul> 164 */ getBlobStoreLeaseUri(Context context, String checksum, long expiryDateSecs)165 public static Uri getBlobStoreLeaseUri(Context context, String checksum, long expiryDateSecs) 166 throws IOException { 167 return BlobUri.builder(context).setLeaseParameters(checksum, expiryDateSecs).build(); 168 } 169 170 /** 171 * Returns the "blobstore" scheme URI used to release all the leases owned by the calling package. 172 */ getBlobStoreAllLeasesUri(Context context)173 public static Uri getBlobStoreAllLeasesUri(Context context) throws IOException { 174 return BlobUri.builder(context).setAllLeasesParameters().build(); 175 } 176 177 /** 178 * Returns {@code basename.extension}, with {@code instanceId} appended to basename if present. 179 * 180 * <p>Useful for building filenames that must be distinguished by InstanceId while keeping the 181 * same basename and file extension. 182 */ buildFilename( String basename, String extension, Optional<String> instanceId)183 public static String buildFilename( 184 String basename, String extension, Optional<String> instanceId) { 185 String resultBasename = basename; 186 if (instanceId != null && instanceId.isPresent()) { 187 resultBasename += instanceId.get(); 188 } 189 return resultBasename + "." + extension; 190 } 191 192 /** Convenience method to get the storage subdirectory based on the allowed readers. */ getSubDirectory(AllowedReaders allowedReaders)193 private static String getSubDirectory(AllowedReaders allowedReaders) { 194 switch (allowedReaders) { 195 case ALL_GOOGLE_APPS: 196 return MDD_STORAGE_ALL_GOOGLE_APPS; 197 case ONLY_GOOGLE_PLAY_SERVICES: 198 return MDD_STORAGE_ONLY_GOOGLE_PLAY_SERVICES; 199 case ALL_APPS: 200 return MDD_STORAGE_ALL_APPS; 201 } 202 throw new IllegalArgumentException("invalid allowed readers value"); 203 } 204 } 205