1 /* 2 * Copyright 2021 The Android Open Source Project 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.exoplayer2.transformer; 17 18 import static com.google.android.exoplayer2.util.Assertions.checkState; 19 20 import android.content.Context; 21 import android.os.Build; 22 import com.google.android.exoplayer2.Format; 23 import com.google.android.exoplayer2.util.Log; 24 import com.google.android.exoplayer2.util.Util; 25 import java.io.File; 26 import java.io.FileWriter; 27 import java.io.IOException; 28 import java.util.List; 29 import org.json.JSONException; 30 import org.json.JSONObject; 31 32 /** Utilities for instrumentation tests. */ 33 public final class AndroidTestUtil { 34 public static final String MP4_ASSET_URI_STRING = "asset:///media/mp4/sample.mp4"; 35 public static final String MP4_ASSET_WITH_INCREASING_TIMESTAMPS_URI_STRING = 36 "asset:///media/mp4/sample_with_increasing_timestamps.mp4"; 37 public static final String MP4_ASSET_SEF_URI_STRING = 38 "asset:///media/mp4/sample_sef_slow_motion.mp4"; 39 public static final String MP4_REMOTE_10_SECONDS_URI_STRING = 40 "https://storage.googleapis.com/exoplayer-test-media-1/mp4/android-screens-10s.mp4"; 41 /** Test clip transcoded from {@link #MP4_REMOTE_10_SECONDS_URI_STRING} with H264 and MP3. */ 42 public static final String MP4_REMOTE_H264_MP3_URI_STRING = 43 "https://storage.googleapis.com/exoplayer-test-media-1/mp4/%20android-screens-10s-h264-mp3.mp4"; 44 45 public static final String MP4_REMOTE_4K60_PORTRAIT_URI_STRING = 46 "https://storage.googleapis.com/exoplayer-test-media-1/mp4/portrait_4k60.mp4"; 47 48 /** 49 * Log in logcat and in an analysis file that this test was skipped. 50 * 51 * <p>Analysis file is a JSON summarising the test, saved to the application cache. 52 * 53 * <p>The analysis json will contain a {@code skipReason} key, with the reason for skipping the 54 * test case. 55 */ recordTestSkipped(Context context, String testId, String reason)56 public static void recordTestSkipped(Context context, String testId, String reason) 57 throws JSONException, IOException { 58 Log.i(testId, reason); 59 JSONObject testJson = new JSONObject(); 60 testJson.put("skipReason", reason); 61 62 writeTestSummaryToFile(context, testId, testJson); 63 } 64 65 /** 66 * A {@link Codec.EncoderFactory} that forces encoding, wrapping {@link DefaultEncoderFactory}. 67 */ 68 public static final Codec.EncoderFactory FORCE_ENCODE_ENCODER_FACTORY = 69 new Codec.EncoderFactory() { 70 @Override 71 public Codec createForAudioEncoding(Format format, List<String> allowedMimeTypes) 72 throws TransformationException { 73 return Codec.EncoderFactory.DEFAULT.createForAudioEncoding(format, allowedMimeTypes); 74 } 75 76 @Override 77 public Codec createForVideoEncoding(Format format, List<String> allowedMimeTypes) 78 throws TransformationException { 79 return Codec.EncoderFactory.DEFAULT.createForVideoEncoding(format, allowedMimeTypes); 80 } 81 82 @Override 83 public boolean audioNeedsEncoding() { 84 return true; 85 } 86 87 @Override 88 public boolean videoNeedsEncoding() { 89 return true; 90 } 91 }; 92 93 /** 94 * Returns a {@link JSONObject} containing device specific details from {@link Build}, including 95 * manufacturer, model, SDK version and build fingerprint. 96 */ getDeviceDetailsAsJsonObject()97 public static JSONObject getDeviceDetailsAsJsonObject() throws JSONException { 98 return new JSONObject() 99 .put("manufacturer", Build.MANUFACTURER) 100 .put("model", Build.MODEL) 101 .put("sdkVersion", Build.VERSION.SDK_INT) 102 .put("fingerprint", Build.FINGERPRINT); 103 } 104 105 /** 106 * Converts an exception to a {@link JSONObject}. 107 * 108 * <p>If the exception is a {@link TransformationException}, {@code errorCode} is included. 109 */ exceptionAsJsonObject(Exception exception)110 public static JSONObject exceptionAsJsonObject(Exception exception) throws JSONException { 111 JSONObject exceptionJson = new JSONObject(); 112 exceptionJson.put("message", exception.getMessage()); 113 exceptionJson.put("type", exception.getClass()); 114 if (exception instanceof TransformationException) { 115 exceptionJson.put("errorCode", ((TransformationException) exception).errorCode); 116 } 117 exceptionJson.put("stackTrace", Log.getThrowableString(exception)); 118 return exceptionJson; 119 } 120 121 /** 122 * Writes the summary of a test run to the application cache file. 123 * 124 * <p>The cache filename follows the pattern {@code <testId>-result.txt}. 125 * 126 * @param context The {@link Context}. 127 * @param testId A unique identifier for the transformer test run. 128 * @param testJson A {@link JSONObject} containing a summary of the test run. 129 */ writeTestSummaryToFile(Context context, String testId, JSONObject testJson)130 public static void writeTestSummaryToFile(Context context, String testId, JSONObject testJson) 131 throws IOException, JSONException { 132 testJson.put("testId", testId).put("device", getDeviceDetailsAsJsonObject()); 133 134 String analysisContents = testJson.toString(/* indentSpaces= */ 2); 135 136 // Log contents as well as writing to file, for easier visibility on individual device testing. 137 for (String line : Util.split(analysisContents, "\n")) { 138 Log.i(testId, line); 139 } 140 141 File analysisFile = createExternalCacheFile(context, /* fileName= */ testId + "-result.txt"); 142 try (FileWriter fileWriter = new FileWriter(analysisFile)) { 143 fileWriter.write(analysisContents); 144 } 145 } 146 147 /** 148 * Creates a {@link File} of the {@code fileName} in the application cache directory. 149 * 150 * <p>If a file of that name already exists, it is overwritten. 151 */ createExternalCacheFile(Context context, String fileName)152 /* package */ static File createExternalCacheFile(Context context, String fileName) 153 throws IOException { 154 File file = new File(context.getExternalCacheDir(), fileName); 155 checkState(!file.exists() || file.delete(), "Could not delete file: " + file.getAbsolutePath()); 156 checkState(file.createNewFile(), "Could not create file: " + file.getAbsolutePath()); 157 return file; 158 } 159 AndroidTestUtil()160 private AndroidTestUtil() {} 161 } 162