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 android.text.TextUtils; 21 import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage; 22 import com.google.android.libraries.mobiledatadownload.file.common.LimitExceededException; 23 import com.google.android.libraries.mobiledatadownload.file.common.MalformedUriException; 24 import com.google.android.libraries.mobiledatadownload.file.common.UnsupportedFileStorageOperation; 25 import com.google.android.libraries.mobiledatadownload.file.openers.ReadStreamOpener; 26 import com.google.android.libraries.mobiledatadownload.file.openers.WriteStreamOpener; 27 import com.google.android.libraries.mobiledatadownload.internal.logging.LogUtil; 28 import com.google.common.io.ByteStreams; 29 import com.google.mobiledatadownload.internal.MetadataProto.DataFile; 30 import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal; 31 import java.io.IOException; 32 import java.io.InputStream; 33 import java.io.OutputStream; 34 35 /** Utils for Android file sharing. */ 36 public final class AndroidSharingUtil { 37 38 private static final String TAG = "AndroidSharingUtil"; 39 40 /** 41 * Exception thrown if an eror occurs while trying to share a file with the Android Blob Sharing 42 * Service. 43 */ 44 public static final class AndroidSharingException extends Exception { 45 // The error code to be logged. 46 private final int errorCode; 47 AndroidSharingException(int errorCode, String message)48 public AndroidSharingException(int errorCode, String message) { 49 super(message); 50 this.errorCode = errorCode; 51 } 52 getErrorCode()53 public int getErrorCode() { 54 return errorCode; 55 } 56 } 57 AndroidSharingUtil()58 private AndroidSharingUtil() {} 59 60 /** Returns true if a blob with checksum {@code checksum} already exists in the shared storage. */ blobExists( Context context, String checksum, DataFileGroupInternal fileGroup, DataFile dataFile, SynchronousFileStorage fileStorage)61 public static boolean blobExists( 62 Context context, 63 String checksum, 64 DataFileGroupInternal fileGroup, 65 DataFile dataFile, 66 SynchronousFileStorage fileStorage) 67 throws AndroidSharingException { 68 int errorCode = -1; 69 boolean exists = false; 70 String message = ""; 71 try { 72 Uri blobUri = DirectoryUtil.getBlobUri(context, checksum); 73 exists = fileStorage.exists(blobUri); 74 } catch (UnsupportedFileStorageOperation e) { 75 String msg = TextUtils.isEmpty(e.getMessage()) ? "" : e.getMessage(); 76 LogUtil.v( 77 "%s: Failed to share for file %s, file group %s." 78 + " UnsupportedFileStorageOperation was thrown with message \"%s\"", 79 TAG, dataFile.getFileId(), fileGroup.getGroupName(), msg); 80 errorCode = 0; 81 message = "UnsupportedFileStorageOperation was thrown: " + msg; 82 } catch (MalformedUriException e) { 83 LogUtil.e( 84 "%s: Malformed lease uri file %s, file group %s", 85 TAG, dataFile.getFileId(), fileGroup.getGroupName()); 86 errorCode = 0; 87 message = 88 String.format( 89 "Malformed blob Uri for file %s, group %s", 90 dataFile.getFileId(), fileGroup.getGroupName()); 91 } catch (IOException e) { 92 LogUtil.e( 93 "%s: Failed to check existence in the shared storage for file %s, file group" + " %s", 94 TAG, dataFile.getFileId(), fileGroup.getGroupName()); 95 errorCode = 0; 96 message = 97 String.format( 98 "Error while checking if file %s, group %s, exists in the shared blob storage.", 99 dataFile.getFileId(), fileGroup.getGroupName()); 100 } 101 if (errorCode != -1) { 102 throw new AndroidSharingException(errorCode, message); 103 } 104 return exists; 105 } 106 107 /** 108 * Copies the local {@code downloadFileOnDeviceUri} to the blob storage. 109 * 110 * @param afterDownload whether this function is called before or after the {@code dataFile}'s 111 * download. 112 */ copyFileToBlobStore( Context context, String checksum, Uri downloadFileOnDeviceUri, DataFileGroupInternal fileGroup, DataFile dataFile, SynchronousFileStorage fileStorage, boolean afterDownload)113 public static void copyFileToBlobStore( 114 Context context, 115 String checksum, 116 Uri downloadFileOnDeviceUri, 117 DataFileGroupInternal fileGroup, 118 DataFile dataFile, 119 SynchronousFileStorage fileStorage, 120 boolean afterDownload) 121 throws AndroidSharingException { 122 int errorCode = -1; 123 String message = ""; 124 try { 125 Uri blobUri = DirectoryUtil.getBlobUri(context, checksum); 126 try (InputStream in = fileStorage.open(downloadFileOnDeviceUri, ReadStreamOpener.create()); 127 OutputStream out = fileStorage.open(blobUri, WriteStreamOpener.create())) { 128 ByteStreams.copy(in, out); 129 } 130 } catch (UnsupportedFileStorageOperation e) { 131 String msg = TextUtils.isEmpty(e.getMessage()) ? "" : e.getMessage(); 132 LogUtil.v( 133 "%s: Failed to share after download for file %s, file group %s." 134 + " UnsupportedFileStorageOperation was thrown with message \"%s\"", 135 TAG, dataFile.getFileId(), fileGroup.getGroupName(), msg); 136 errorCode = 0; 137 message = "UnsupportedFileStorageOperation was thrown: " + msg; 138 } catch (LimitExceededException e) { 139 LogUtil.e( 140 "%s: Failed to share after download for file %s, file group %s due to" 141 + " LimitExceededException", 142 TAG, dataFile.getFileId(), fileGroup.getGroupName()); 143 errorCode = 0; 144 message = 145 String.format( 146 "System limit exceeded for file %s, group %s", 147 dataFile.getFileId(), fileGroup.getGroupName()); 148 } catch (MalformedUriException e) { 149 LogUtil.e( 150 "%s: Malformed lease uri file %s, file group %s", 151 TAG, dataFile.getFileId(), fileGroup.getGroupName()); 152 errorCode = 0; 153 message = 154 String.format( 155 "Malformed blob Uri for file %s, group %s", 156 dataFile.getFileId(), fileGroup.getGroupName()); 157 } catch (IOException e) { 158 LogUtil.e( 159 "%s: Failed to copy to the blobstore after download for file %s, file group" + " %s", 160 TAG, dataFile.getFileId(), fileGroup.getGroupName()); 161 errorCode = afterDownload ? 0 : 0; 162 message = 163 String.format( 164 "Error while copying file %s, group %s, to the shared blob storage", 165 dataFile.getFileId(), fileGroup.getGroupName()); 166 } 167 if (errorCode != -1) { 168 throw new AndroidSharingException(errorCode, message); 169 } 170 } 171 172 /** Acquires the lease on the shared {@code dataFile}. */ acquireLease( Context context, String checksum, long expiryDate, DataFileGroupInternal fileGroup, DataFile dataFile, SynchronousFileStorage fileStorage)173 public static void acquireLease( 174 Context context, 175 String checksum, 176 long expiryDate, 177 DataFileGroupInternal fileGroup, 178 DataFile dataFile, 179 SynchronousFileStorage fileStorage) 180 throws AndroidSharingException { 181 int errorCode = -1; 182 String message = ""; 183 try { 184 Uri leaseUri = DirectoryUtil.getBlobStoreLeaseUri(context, checksum, expiryDate); 185 // Acquires/updates the lease to the blob. 186 // TODO(b/149260496): catch LimitExceededException, thrown when a lease could not be acquired, 187 // such as when the caller is trying to acquire leases on too much data. 188 try (OutputStream out = fileStorage.open(leaseUri, WriteStreamOpener.create())) {} 189 190 } catch (UnsupportedFileStorageOperation e) { 191 String msg = TextUtils.isEmpty(e.getMessage()) ? "" : e.getMessage(); 192 LogUtil.v( 193 "%s: Failed to share file %s, file group %s." 194 + " UnsupportedFileStorageOperation was thrown with message \"%s\"", 195 TAG, dataFile.getFileId(), fileGroup.getGroupName(), msg); 196 errorCode = 0; 197 message = "UnsupportedFileStorageOperation was thrown: " + msg; 198 } catch (MalformedUriException e) { 199 LogUtil.e( 200 "%s: Malformed lease uri file %s, file group %s", 201 TAG, dataFile.getFileId(), fileGroup.getGroupName()); 202 errorCode = 0; 203 message = 204 String.format( 205 "Malformed lease Uri for file %s, group %s", 206 dataFile.getFileId(), fileGroup.getGroupName()); 207 } catch (LimitExceededException e) { 208 LogUtil.e( 209 "%s: Failed to share after download for file %s, file group %s due to" 210 + " LimitExceededException", 211 TAG, dataFile.getFileId(), fileGroup.getGroupName()); 212 errorCode = 0; 213 message = 214 String.format( 215 "System limit exceeded for file %s, group %s", 216 dataFile.getFileId(), fileGroup.getGroupName()); 217 } catch (IOException e) { 218 LogUtil.e( 219 "%s: Failed to acquire lease for file %s, file group" + " %s", 220 TAG, dataFile.getFileId(), fileGroup.getGroupName()); 221 errorCode = 0; 222 message = 223 String.format( 224 "Error while acquiring lease for file %s, group %s", 225 dataFile.getFileId(), fileGroup.getGroupName()); 226 } 227 if (errorCode != -1) { 228 throw new AndroidSharingException(errorCode, message); 229 } 230 } 231 } 232