/* * 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; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.MILLISECONDS; import android.content.Context; import android.os.Build.VERSION; import android.util.Log; import androidx.test.uiautomator.UiDevice; import com.google.mobiledatadownload.internal.MetadataProto.BaseFile; import com.google.mobiledatadownload.internal.MetadataProto.DataFile; import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal; import com.google.mobiledatadownload.internal.MetadataProto.DeltaFile; import com.google.mobiledatadownload.internal.MetadataProto.DeltaFile.DiffDecoder; import com.google.mobiledatadownload.internal.MetadataProto.FileStatus; import com.google.mobiledatadownload.internal.MetadataProto.NewFileKey; import com.google.mobiledatadownload.internal.MetadataProto.SharedFile; import com.google.mobiledatadownload.DownloadConfigProto.DataFileGroup; import com.google.mobiledatadownload.TransformProto.Transform; import com.google.mobiledatadownload.TransformProto.Transforms; import com.google.mobiledatadownload.TransformProto.ZipTransform; import com.google.protobuf.MessageLite; import java.io.IOException; import java.util.Collections; import java.util.List; public class MddTestUtil { public static final String FILE_URI = "android://file"; private static final String TAG = "MddTestUtil"; /** * Creates a data file group with the given number of files. It only sets the field that are set * in a data file group that we get from the server. */ public static DataFileGroup createDataFileGroup(String fileGroupName, int fileCount) { DataFileGroup.Builder dataFileGroup = DataFileGroup.newBuilder().setGroupName(fileGroupName); for (int i = 0; i < fileCount; ++i) { com.google.mobiledatadownload.DownloadConfigProto.DataFile.Builder file = com.google.mobiledatadownload.DownloadConfigProto.DataFile.newBuilder(); file.setFileId(String.format("%s_%s", fileGroupName, i)); file.setUrlToDownload(String.format("https://%s_%s", fileGroupName, i)); file.setByteSize(10 + i); file.setChecksum("123" + i); dataFileGroup.addFile(file.build()); } return dataFileGroup.build(); } /** * Creates an internal data file group with the given number of files. It only sets the field that * are set in a data file group that we get from the server. */ public static DataFileGroupInternal createDataFileGroupInternal( String fileGroupName, int fileCount) { DataFileGroupInternal.Builder dataFileGroupInternal = DataFileGroupInternal.newBuilder().setGroupName(fileGroupName); for (int i = 0; i < fileCount; ++i) { dataFileGroupInternal.addFile(createDataFile(fileGroupName, i)); } return dataFileGroupInternal.build(); } /** * Creates an internal data file group with the given number of files. It only sets the field that * are set in a data file group that we get from the server. */ public static DataFileGroupInternal createDataFileGroupInternalWithDownloadId( String fileGroupName, int fileCount) { DataFileGroupInternal.Builder dataFileGroupInternal = DataFileGroupInternal.newBuilder().setGroupName(fileGroupName); for (int i = 0; i < fileCount; ++i) { dataFileGroupInternal.addFile(createDataFile(fileGroupName, i)); } return dataFileGroupInternal.build(); } /** * Creates an internal data file group with the given number of files, all configured to be * shared. It only sets the field that are set in a data file group that we get from the server. */ public static DataFileGroupInternal createSharedDataFileGroupInternal( String fileGroupName, int fileCount) { DataFileGroupInternal.Builder dataFileGroupInternal = DataFileGroupInternal.newBuilder().setGroupName(fileGroupName); for (int i = 0; i < fileCount; ++i) { dataFileGroupInternal.addFile(createSharedDataFile(fileGroupName, /* fileIndex= */ i)); } return dataFileGroupInternal.build(); } /** * Creates a data file group with the given number of files. It creates a downloaded file, so the * file uri is also set. */ public static DataFileGroupInternal createDownloadedDataFileGroupInternal( String fileGroupName, int fileCount) { DataFileGroupInternal.Builder dataFileGroup = DataFileGroupInternal.newBuilder().setGroupName(fileGroupName); for (int i = 0; i < fileCount; ++i) { dataFileGroup.addFile(createDownloadedDataFile(fileGroupName, i)); } return dataFileGroup.build(); } private static DataFile createDownloadedDataFile(String fileId, int fileIndex) { DataFile file = createDataFile(fileId, fileIndex); return file; } public static DataFile createDataFile(String fileId, int fileIndex) { DataFile.Builder file = DataFile.newBuilder(); file.setFileId(String.format("%s_%s", fileId, fileIndex)); file.setUrlToDownload(String.format("https://%s_%s", fileId, fileIndex)); file.setByteSize(10 + fileIndex); file.setChecksum("123" + fileIndex); return file.build(); } /** * Creates a dataFile configured for sharing, i.e. with ChecksumType set to SHA256 and * AndroidSharingType set to ANDROID_BLOB_WHEN_AVAILABLE. */ public static DataFile createSharedDataFile(String fileId, int fileIndex) { DataFile.Builder file = DataFile.newBuilder(); file.setFileId(String.format("%s_%s", fileId, fileIndex)); file.setUrlToDownload(String.format("https://%s_%s", fileId, fileIndex)); file.setByteSize(10 + fileIndex); file.setChecksum("123" + fileIndex); file.setChecksumType(DataFile.ChecksumType.DEFAULT); file.setAndroidSharingType(DataFile.AndroidSharingType.ANDROID_BLOB_WHEN_AVAILABLE); file.setAndroidSharingChecksumType(DataFile.AndroidSharingChecksumType.SHA2_256); file.setAndroidSharingChecksum("sha256_123" + fileIndex); return file.build(); } /** Creates a dataFile with relative path. */ public static DataFile createRelativePathDataFile( String fileId, int fileIndex, String relativeFilePath) { return DataFile.newBuilder() .setFileId(String.format("%s_%s", fileId, fileIndex)) .setUrlToDownload(String.format("https://%s_%s", fileId, fileIndex)) .setByteSize(10 + fileIndex) .setChecksum("123" + fileIndex) .setChecksumType(DataFile.ChecksumType.DEFAULT) .setRelativeFilePath(relativeFilePath) .build(); } public static DataFile createZipFolderDataFile(String fileId, int fileIndex) { DataFile.Builder file = DataFile.newBuilder(); file.setFileId(String.format("%s_%s", fileId, fileIndex)); file.setUrlToDownload(String.format("https://%s_%s", fileId, fileIndex)); file.setByteSize(10 + fileIndex); file.setDownloadedFileChecksum("123" + fileIndex); file.setDownloadTransforms( Transforms.newBuilder() .addTransform( Transform.newBuilder().setZip(ZipTransform.newBuilder().setTarget("*").build()))); return file.build(); } public static DataFileGroupInternal createFileGroupInternalWithDeltaFile(String fileGroupName) { DataFileGroupInternal.Builder fileGroupBuilder = MddTestUtil.createDataFileGroupInternal(fileGroupName, 1).toBuilder(); DataFileGroupInternal dataFileGroup = fileGroupBuilder .setFile(0, fileGroupBuilder.getFile(0).toBuilder().addDeltaFile(0, createDeltaFile())) .build(); return dataFileGroup; } public static DeltaFile createDeltaFile() { return DeltaFile.newBuilder() .setUrlToDownload("http://abc") .setByteSize(10) .setChecksum("ABC") .setDiffDecoder(DiffDecoder.VC_DIFF) .setBaseFile(createDeltaBaseFile("mychecksum")) .build(); } public static DeltaFile createDeltaFile(String fileId, int fileIndex) { return DeltaFile.newBuilder() .setUrlToDownload(String.format("https://%s_%s", fileId, fileIndex)) .setByteSize(10 + fileIndex) .setChecksum("123" + fileIndex) .setDiffDecoder(DiffDecoder.VC_DIFF) .setBaseFile(createDeltaBaseFile("mychecksum" + fileIndex)) .build(); } public static BaseFile createDeltaBaseFile(String checksum) { return BaseFile.newBuilder().setChecksum(checksum).build(); } public static NewFileKey[] createFileKeysForDataFileGroupInternal(DataFileGroupInternal group) { NewFileKey[] newFileKeys = new NewFileKey[group.getFileCount()]; for (int i = 0; i < group.getFileCount(); ++i) { newFileKeys[i] = SharedFilesMetadata.createKeyFromDataFile( group.getFile(i), group.getAllowedReadersEnum()); } return newFileKeys; } public static void assertMessageEquals(MessageLite expected, MessageLite actual) { assertWithMessage(String.format("EXPECTED: %s\n ACTUAL: %s\n", expected, actual)) .that(expected.equals(actual)) .isTrue(); } public static DataFile createDataFileWithDeltaFile( String fileId, int fileIndex, int deltaFileCount) { DataFile.Builder file = DataFile.newBuilder() .setFileId(String.format("%s_%s", fileId, fileIndex)) .setUrlToDownload(String.format("https://%s_%s", fileId, fileIndex)) .setByteSize(10 + fileIndex) .setChecksum("123" + fileIndex); for (int i = 0; i < deltaFileCount; i++) { file.addDeltaFile(createDeltaFile(fileId + "_delta_" + i, i)); } return file.build(); } /** Executes the shell command {@code cmd}. */ public static String runShellCmd(String cmd) throws IOException { final UiDevice uiDevice = UiDevice.getInstance(getInstrumentation()); final String result = uiDevice.executeShellCommand(cmd).trim(); Log.i(TAG, "Output of '" + cmd + "': '" + result + "'"); return result; } /** For API-level 19+, it moves the time forward by {@code timeInMillis} milliseconds. */ // public static void timeTravel(Context context, long timeInMillis) { // if (VERSION.SDK_INT == 18) { // throw new UnsupportedOperationException( // "Time travel does not work on API-level 18 - b/31132161. " // + "You need to disable this test on API-level 18. Example: cl/131498720"); // } // // final long timestampBeforeTravel = System.currentTimeMillis(); // if (!BackdoorTestUtil.advanceTime(context, timeInMillis)) { // // On some API levels (>23) the call returns false even if the time changed. Have a manual // // validation that the time changed instead. // if (VERSION.SDK_INT >= 23) { // assertThat(System.currentTimeMillis()).isAtLeast(timestampBeforeTravel + timeInMillis); // } else { // throw new IllegalStateException("Time Travel was not successful"); // } // } // } /** * @return the time (in seconds) that is n days from the current time */ public static long daysFromNow(int days) { long thenMillis = System.currentTimeMillis() + DAYS.toMillis(days); return MILLISECONDS.toSeconds(thenMillis); } /** * Writes the SharedFile metadata for all the files stored in the {@code fileGroup}, setting for * each of them the status and whether they are currently android-shared based on the array * position. */ public static void writeSharedFiles( SharedFilesMetadata sharedFilesMetadata, DataFileGroupInternal fileGroup, List statuses, List androidShared) throws Exception { int size = fileGroup.getFileCount(); NewFileKey[] keys = createFileKeysForDataFileGroupInternal(fileGroup); assertWithMessage("Created file keys must match the given DataFileGroup's file count") .that(keys.length) .isEqualTo(size); assertWithMessage("Given FileStatus list must match the given DataFileGroup's file count") .that(statuses.size()) .isEqualTo(size); assertWithMessage("Given androidShared list must match the given DataFileGroup's file count") .that(androidShared.size()) .isEqualTo(size); for (int i = 0; i < fileGroup.getFileCount(); i++) { DataFile file = fileGroup.getFile(i); SharedFile.Builder sharedFileBuilder = SharedFile.newBuilder().setFileName(file.getFileId()).setFileStatus(statuses.get(i)); if (androidShared.get(i)) { sharedFileBuilder.setAndroidShared(true).setAndroidSharingChecksum("sha256_123" + i); } sharedFilesMetadata.write(keys[i], sharedFileBuilder.build()).get(); } } /** * Convenience method for {@link MddTestUtil#writeSharedFiles(SharedFilesMetadata, * DataFileGroupInternal, List, List)} when android shared status is * unnecessary. */ public static void writeSharedFiles( SharedFilesMetadata sharedFilesMetadata, DataFileGroupInternal fileGroup, List statuses) throws Exception { int size = fileGroup.getFileCount(); List androidShared = Collections.nCopies(size, false); writeSharedFiles(sharedFilesMetadata, fileGroup, statuses, androidShared); } }