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; 17 18 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 19 import static com.google.common.truth.Truth.assertThat; 20 import static com.google.common.truth.Truth.assertWithMessage; 21 import static java.util.concurrent.TimeUnit.DAYS; 22 import static java.util.concurrent.TimeUnit.MILLISECONDS; 23 24 import android.content.Context; 25 import android.os.Build.VERSION; 26 import android.support.test.uiautomator.UiDevice; 27 import android.util.Log; 28 import com.google.mobiledatadownload.internal.MetadataProto.BaseFile; 29 import com.google.mobiledatadownload.internal.MetadataProto.DataFile; 30 import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal; 31 import com.google.mobiledatadownload.internal.MetadataProto.DeltaFile; 32 import com.google.mobiledatadownload.internal.MetadataProto.DeltaFile.DiffDecoder; 33 import com.google.mobiledatadownload.internal.MetadataProto.FileStatus; 34 import com.google.mobiledatadownload.internal.MetadataProto.NewFileKey; 35 import com.google.mobiledatadownload.internal.MetadataProto.SharedFile; 36 import com.google.mobiledatadownload.DownloadConfigProto.DataFileGroup; 37 import com.google.mobiledatadownload.TransformProto.Transform; 38 import com.google.mobiledatadownload.TransformProto.Transforms; 39 import com.google.mobiledatadownload.TransformProto.ZipTransform; 40 import com.google.protobuf.MessageLite; 41 import java.io.IOException; 42 import java.util.Collections; 43 import java.util.List; 44 45 public class MddTestUtil { 46 47 public static final String FILE_URI = "android://file"; 48 private static final String TAG = "MddTestUtil"; 49 50 /** 51 * Creates a data file group with the given number of files. It only sets the field that are set 52 * in a data file group that we get from the server. 53 */ createDataFileGroup(String fileGroupName, int fileCount)54 public static DataFileGroup createDataFileGroup(String fileGroupName, int fileCount) { 55 DataFileGroup.Builder dataFileGroup = DataFileGroup.newBuilder().setGroupName(fileGroupName); 56 for (int i = 0; i < fileCount; ++i) { 57 com.google.mobiledatadownload.DownloadConfigProto.DataFile.Builder file = 58 com.google.mobiledatadownload.DownloadConfigProto.DataFile.newBuilder(); 59 file.setFileId(String.format("%s_%s", fileGroupName, i)); 60 file.setUrlToDownload(String.format("https://%s_%s", fileGroupName, i)); 61 file.setByteSize(10 + i); 62 file.setChecksum("123" + i); 63 dataFileGroup.addFile(file.build()); 64 } 65 return dataFileGroup.build(); 66 } 67 68 /** 69 * Creates an internal data file group with the given number of files. It only sets the field that 70 * are set in a data file group that we get from the server. 71 */ createDataFileGroupInternal( String fileGroupName, int fileCount)72 public static DataFileGroupInternal createDataFileGroupInternal( 73 String fileGroupName, int fileCount) { 74 DataFileGroupInternal.Builder dataFileGroupInternal = 75 DataFileGroupInternal.newBuilder().setGroupName(fileGroupName); 76 for (int i = 0; i < fileCount; ++i) { 77 dataFileGroupInternal.addFile(createDataFile(fileGroupName, i)); 78 } 79 return dataFileGroupInternal.build(); 80 } 81 82 /** 83 * Creates an internal data file group with the given number of files. It only sets the field that 84 * are set in a data file group that we get from the server. 85 */ createDataFileGroupInternalWithDownloadId( String fileGroupName, int fileCount)86 public static DataFileGroupInternal createDataFileGroupInternalWithDownloadId( 87 String fileGroupName, int fileCount) { 88 DataFileGroupInternal.Builder dataFileGroupInternal = 89 DataFileGroupInternal.newBuilder().setGroupName(fileGroupName); 90 for (int i = 0; i < fileCount; ++i) { 91 dataFileGroupInternal.addFile(createDataFile(fileGroupName, i)); 92 } 93 return dataFileGroupInternal.build(); 94 } 95 96 /** 97 * Creates an internal data file group with the given number of files, all configured to be 98 * shared. It only sets the field that are set in a data file group that we get from the server. 99 */ createSharedDataFileGroupInternal( String fileGroupName, int fileCount)100 public static DataFileGroupInternal createSharedDataFileGroupInternal( 101 String fileGroupName, int fileCount) { 102 DataFileGroupInternal.Builder dataFileGroupInternal = 103 DataFileGroupInternal.newBuilder().setGroupName(fileGroupName); 104 for (int i = 0; i < fileCount; ++i) { 105 dataFileGroupInternal.addFile(createSharedDataFile(fileGroupName, /* fileIndex= */ i)); 106 } 107 return dataFileGroupInternal.build(); 108 } 109 110 /** 111 * Creates a data file group with the given number of files. It creates a downloaded file, so the 112 * file uri is also set. 113 */ createDownloadedDataFileGroupInternal( String fileGroupName, int fileCount)114 public static DataFileGroupInternal createDownloadedDataFileGroupInternal( 115 String fileGroupName, int fileCount) { 116 DataFileGroupInternal.Builder dataFileGroup = 117 DataFileGroupInternal.newBuilder().setGroupName(fileGroupName); 118 for (int i = 0; i < fileCount; ++i) { 119 dataFileGroup.addFile(createDownloadedDataFile(fileGroupName, i)); 120 } 121 return dataFileGroup.build(); 122 } 123 createDownloadedDataFile(String fileId, int fileIndex)124 private static DataFile createDownloadedDataFile(String fileId, int fileIndex) { 125 DataFile file = createDataFile(fileId, fileIndex); 126 return file; 127 } 128 createDataFile(String fileId, int fileIndex)129 public static DataFile createDataFile(String fileId, int fileIndex) { 130 DataFile.Builder file = DataFile.newBuilder(); 131 file.setFileId(String.format("%s_%s", fileId, fileIndex)); 132 file.setUrlToDownload(String.format("https://%s_%s", fileId, fileIndex)); 133 file.setByteSize(10 + fileIndex); 134 file.setChecksum("123" + fileIndex); 135 return file.build(); 136 } 137 138 /** 139 * Creates a dataFile configured for sharing, i.e. with ChecksumType set to SHA256 and 140 * AndroidSharingType set to ANDROID_BLOB_WHEN_AVAILABLE. 141 */ createSharedDataFile(String fileId, int fileIndex)142 public static DataFile createSharedDataFile(String fileId, int fileIndex) { 143 DataFile.Builder file = DataFile.newBuilder(); 144 file.setFileId(String.format("%s_%s", fileId, fileIndex)); 145 file.setUrlToDownload(String.format("https://%s_%s", fileId, fileIndex)); 146 file.setByteSize(10 + fileIndex); 147 file.setChecksum("123" + fileIndex); 148 file.setChecksumType(DataFile.ChecksumType.DEFAULT); 149 file.setAndroidSharingType(DataFile.AndroidSharingType.ANDROID_BLOB_WHEN_AVAILABLE); 150 file.setAndroidSharingChecksumType(DataFile.AndroidSharingChecksumType.SHA2_256); 151 file.setAndroidSharingChecksum("sha256_123" + fileIndex); 152 return file.build(); 153 } 154 155 /** Creates a dataFile with relative path. */ createRelativePathDataFile( String fileId, int fileIndex, String relativeFilePath)156 public static DataFile createRelativePathDataFile( 157 String fileId, int fileIndex, String relativeFilePath) { 158 return DataFile.newBuilder() 159 .setFileId(String.format("%s_%s", fileId, fileIndex)) 160 .setUrlToDownload(String.format("https://%s_%s", fileId, fileIndex)) 161 .setByteSize(10 + fileIndex) 162 .setChecksum("123" + fileIndex) 163 .setChecksumType(DataFile.ChecksumType.DEFAULT) 164 .setRelativeFilePath(relativeFilePath) 165 .build(); 166 } 167 createZipFolderDataFile(String fileId, int fileIndex)168 public static DataFile createZipFolderDataFile(String fileId, int fileIndex) { 169 DataFile.Builder file = DataFile.newBuilder(); 170 file.setFileId(String.format("%s_%s", fileId, fileIndex)); 171 file.setUrlToDownload(String.format("https://%s_%s", fileId, fileIndex)); 172 file.setByteSize(10 + fileIndex); 173 file.setDownloadedFileChecksum("123" + fileIndex); 174 file.setDownloadTransforms( 175 Transforms.newBuilder() 176 .addTransform( 177 Transform.newBuilder().setZip(ZipTransform.newBuilder().setTarget("*").build()))); 178 return file.build(); 179 } 180 createFileGroupInternalWithDeltaFile(String fileGroupName)181 public static DataFileGroupInternal createFileGroupInternalWithDeltaFile(String fileGroupName) { 182 DataFileGroupInternal.Builder fileGroupBuilder = 183 MddTestUtil.createDataFileGroupInternal(fileGroupName, 1).toBuilder(); 184 DataFileGroupInternal dataFileGroup = 185 fileGroupBuilder 186 .setFile(0, fileGroupBuilder.getFile(0).toBuilder().addDeltaFile(0, createDeltaFile())) 187 .build(); 188 return dataFileGroup; 189 } 190 createDeltaFile()191 public static DeltaFile createDeltaFile() { 192 return DeltaFile.newBuilder() 193 .setUrlToDownload("http://abc") 194 .setByteSize(10) 195 .setChecksum("ABC") 196 .setDiffDecoder(DiffDecoder.VC_DIFF) 197 .setBaseFile(createDeltaBaseFile("mychecksum")) 198 .build(); 199 } 200 createDeltaFile(String fileId, int fileIndex)201 public static DeltaFile createDeltaFile(String fileId, int fileIndex) { 202 return DeltaFile.newBuilder() 203 .setUrlToDownload(String.format("https://%s_%s", fileId, fileIndex)) 204 .setByteSize(10 + fileIndex) 205 .setChecksum("123" + fileIndex) 206 .setDiffDecoder(DiffDecoder.VC_DIFF) 207 .setBaseFile(createDeltaBaseFile("mychecksum" + fileIndex)) 208 .build(); 209 } 210 createDeltaBaseFile(String checksum)211 public static BaseFile createDeltaBaseFile(String checksum) { 212 return BaseFile.newBuilder().setChecksum(checksum).build(); 213 } 214 createFileKeysForDataFileGroupInternal(DataFileGroupInternal group)215 public static NewFileKey[] createFileKeysForDataFileGroupInternal(DataFileGroupInternal group) { 216 NewFileKey[] newFileKeys = new NewFileKey[group.getFileCount()]; 217 for (int i = 0; i < group.getFileCount(); ++i) { 218 newFileKeys[i] = 219 SharedFilesMetadata.createKeyFromDataFile( 220 group.getFile(i), group.getAllowedReadersEnum()); 221 } 222 return newFileKeys; 223 } 224 assertMessageEquals(MessageLite expected, MessageLite actual)225 public static void assertMessageEquals(MessageLite expected, MessageLite actual) { 226 assertWithMessage(String.format("EXPECTED: %s\n ACTUAL: %s\n", expected, actual)) 227 .that(expected.equals(actual)) 228 .isTrue(); 229 } 230 createDataFileWithDeltaFile( String fileId, int fileIndex, int deltaFileCount)231 public static DataFile createDataFileWithDeltaFile( 232 String fileId, int fileIndex, int deltaFileCount) { 233 DataFile.Builder file = 234 DataFile.newBuilder() 235 .setFileId(String.format("%s_%s", fileId, fileIndex)) 236 .setUrlToDownload(String.format("https://%s_%s", fileId, fileIndex)) 237 .setByteSize(10 + fileIndex) 238 .setChecksum("123" + fileIndex); 239 for (int i = 0; i < deltaFileCount; i++) { 240 file.addDeltaFile(createDeltaFile(fileId + "_delta_" + i, i)); 241 } 242 return file.build(); 243 } 244 245 /** Executes the shell command {@code cmd}. */ runShellCmd(String cmd)246 public static String runShellCmd(String cmd) throws IOException { 247 final UiDevice uiDevice = UiDevice.getInstance(getInstrumentation()); 248 final String result = uiDevice.executeShellCommand(cmd).trim(); 249 Log.i(TAG, "Output of '" + cmd + "': '" + result + "'"); 250 return result; 251 } 252 253 /** For API-level 19+, it moves the time forward by {@code timeInMillis} milliseconds. */ 254 // public static void timeTravel(Context context, long timeInMillis) { 255 // if (VERSION.SDK_INT == 18) { 256 // throw new UnsupportedOperationException( 257 // "Time travel does not work on API-level 18 - b/31132161. " 258 // + "You need to disable this test on API-level 18. Example: cl/131498720"); 259 // } 260 // 261 // final long timestampBeforeTravel = System.currentTimeMillis(); 262 // if (!BackdoorTestUtil.advanceTime(context, timeInMillis)) { 263 // // On some API levels (>23) the call returns false even if the time changed. Have a manual 264 // // validation that the time changed instead. 265 // if (VERSION.SDK_INT >= 23) { 266 // assertThat(System.currentTimeMillis()).isAtLeast(timestampBeforeTravel + timeInMillis); 267 // } else { 268 // throw new IllegalStateException("Time Travel was not successful"); 269 // } 270 // } 271 // } 272 273 /** 274 * @return the time (in seconds) that is n days from the current time 275 */ daysFromNow(int days)276 public static long daysFromNow(int days) { 277 long thenMillis = System.currentTimeMillis() + DAYS.toMillis(days); 278 return MILLISECONDS.toSeconds(thenMillis); 279 } 280 281 /** 282 * Writes the SharedFile metadata for all the files stored in the {@code fileGroup}, setting for 283 * each of them the status and whether they are currently android-shared based on the array 284 * position. 285 */ writeSharedFiles( SharedFilesMetadata sharedFilesMetadata, DataFileGroupInternal fileGroup, List<FileStatus> statuses, List<Boolean> androidShared)286 public static void writeSharedFiles( 287 SharedFilesMetadata sharedFilesMetadata, 288 DataFileGroupInternal fileGroup, 289 List<FileStatus> statuses, 290 List<Boolean> androidShared) 291 throws Exception { 292 int size = fileGroup.getFileCount(); 293 NewFileKey[] keys = createFileKeysForDataFileGroupInternal(fileGroup); 294 assertWithMessage("Created file keys must match the given DataFileGroup's file count") 295 .that(keys.length) 296 .isEqualTo(size); 297 assertWithMessage("Given FileStatus list must match the given DataFileGroup's file count") 298 .that(statuses.size()) 299 .isEqualTo(size); 300 assertWithMessage("Given androidShared list must match the given DataFileGroup's file count") 301 .that(androidShared.size()) 302 .isEqualTo(size); 303 for (int i = 0; i < fileGroup.getFileCount(); i++) { 304 DataFile file = fileGroup.getFile(i); 305 SharedFile.Builder sharedFileBuilder = 306 SharedFile.newBuilder().setFileName(file.getFileId()).setFileStatus(statuses.get(i)); 307 if (androidShared.get(i)) { 308 sharedFileBuilder.setAndroidShared(true).setAndroidSharingChecksum("sha256_123" + i); 309 } 310 sharedFilesMetadata.write(keys[i], sharedFileBuilder.build()).get(); 311 } 312 } 313 314 /** 315 * Convenience method for {@link MddTestUtil#writeSharedFiles(SharedFilesMetadata, 316 * DataFileGroupInternal, List<FileStatus>, List<Boolean>)} when android shared status is 317 * unnecessary. 318 */ writeSharedFiles( SharedFilesMetadata sharedFilesMetadata, DataFileGroupInternal fileGroup, List<FileStatus> statuses)319 public static void writeSharedFiles( 320 SharedFilesMetadata sharedFilesMetadata, 321 DataFileGroupInternal fileGroup, 322 List<FileStatus> statuses) 323 throws Exception { 324 int size = fileGroup.getFileCount(); 325 List<Boolean> androidShared = Collections.nCopies(size, false); 326 writeSharedFiles(sharedFilesMetadata, fileGroup, statuses, androidShared); 327 } 328 } 329