Home | History | Annotate | Download | only in shadows
      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