1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; 4 import static org.robolectric.shadow.api.Shadow.directlyOn; 5 6 import android.os.Trace; 7 import android.util.Log; 8 import java.util.ArrayDeque; 9 import java.util.Deque; 10 import java.util.Queue; 11 import javax.annotation.concurrent.GuardedBy; 12 import org.robolectric.annotation.Implementation; 13 import org.robolectric.annotation.Implements; 14 import org.robolectric.annotation.Resetter; 15 import org.robolectric.util.ReflectionHelpers.ClassParameter; 16 17 /** 18 * Shadow implementation for {@link Trace}, which stores the traces locally in arrays (unlike the 19 * real implementation) and allows reading them. 20 * 21 * <p>The shadow doesn't enforce the constrains by default (e.g., null section names, or incorrect 22 * {@link ShadowTrace.beginSection(String)} / {@link ShadowTrace.endSection()} sequences), but can 23 * be configured to do so by calling {@link ShadowTrace.setCrashOnIncorrectUsage(boolean)}. 24 */ 25 @Implements(Trace.class) 26 public class ShadowTrace { 27 private static final String TAG = "ShadowTrace"; 28 29 @GuardedBy("lock") 30 private static final Deque<String> currentSections = new ArrayDeque<>(); 31 32 @GuardedBy("lock") 33 private static final Queue<String> previousSections = new ArrayDeque<>(); 34 35 private static final boolean CRASH_ON_INCORRECT_USAGE_DEFAULT = true; 36 private static boolean crashOnIncorrectUsage = CRASH_ON_INCORRECT_USAGE_DEFAULT; 37 private static boolean appTracingAllowed = true; 38 private static final Object lock = new Object(); 39 40 private static final long TRACE_TAG_APP = 1L << 12; 41 private static final int MAX_SECTION_NAME_LEN = 127; 42 43 /** Starts a new trace section with given name. */ 44 @Implementation(minSdk = JELLY_BEAN_MR2) 45 protected static void beginSection(String sectionName) { 46 if (Trace.isTagEnabled(TRACE_TAG_APP)) { 47 if (crashOnIncorrectUsage) { 48 if (sectionName.length() > MAX_SECTION_NAME_LEN) { 49 throw new IllegalArgumentException("sectionName is too long"); 50 } 51 } else if (sectionName == null) { 52 Log.w(TAG, "Section name cannot be null"); 53 return; 54 } else if (sectionName.length() > MAX_SECTION_NAME_LEN) { 55 Log.w(TAG, "Section name is too long"); 56 return; 57 } 58 59 synchronized (lock) { 60 currentSections.addFirst(sectionName); 61 } 62 } 63 } 64 65 /** 66 * Ends the most recent active trace section. 67 * 68 * @throws {@link AssertionError} if called without any active trace section. 69 */ 70 @Implementation(minSdk = JELLY_BEAN_MR2) 71 protected static void endSection() { 72 if (Trace.isTagEnabled(TRACE_TAG_APP)) { 73 synchronized (lock) { 74 if (currentSections.isEmpty()) { 75 Log.e(TAG, "Trying to end a trace section that was never started"); 76 return; 77 } 78 79 previousSections.offer(currentSections.removeFirst()); 80 } 81 } 82 } 83 84 @Implementation(minSdk = JELLY_BEAN_MR2) 85 protected static boolean isTagEnabled(long traceTag) { 86 if (traceTag == TRACE_TAG_APP) { 87 return appTracingAllowed; 88 } 89 90 return directlyOn(Trace.class, "isTagEnabled", ClassParameter.from(long.class, traceTag)); 91 } 92 93 @Implementation(minSdk = JELLY_BEAN_MR2) 94 protected static void setAppTracingAllowed(boolean appTracingAllowed) { 95 ShadowTrace.appTracingAllowed = appTracingAllowed; 96 } 97 98 /** Returns a stack of the currently active trace sections. */ 99 public static Deque<String> getCurrentSections() { 100 synchronized (lock) { 101 return new ArrayDeque<>(currentSections); 102 } 103 } 104 105 /** Returns a queue of all the previously active trace sections. */ 106 public static Queue<String> getPreviousSections() { 107 synchronized (lock) { 108 return new ArrayDeque<>(previousSections); 109 } 110 } 111 112 /** 113 * Do not use this method unless absolutely necessary. Prefer fixing the tests instead. 114 * 115 * <p>Sets whether to crash on incorrect usage (e.g., calling {@link #endSection()} before {@link 116 * beginSection(String)}. Default value - {@code false}. 117 */ 118 public static void doNotUseSetCrashOnIncorrectUsage(boolean crashOnIncorrectUsage) { 119 ShadowTrace.crashOnIncorrectUsage = crashOnIncorrectUsage; 120 } 121 122 /** Resets internal lists of active trace sections. */ 123 @Resetter 124 public static void reset() { 125 synchronized (lock) { 126 currentSections.clear(); 127 previousSections.clear(); 128 } 129 crashOnIncorrectUsage = CRASH_ON_INCORRECT_USAGE_DEFAULT; 130 } 131 } 132