1 /* 2 * Copyright (C) 2018 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 17 package com.google.android.setupcompat.logging; 18 19 import android.annotation.SuppressLint; 20 import android.content.Context; 21 import androidx.annotation.NonNull; 22 import androidx.annotation.VisibleForTesting; 23 import com.google.android.setupcompat.internal.Preconditions; 24 import com.google.android.setupcompat.internal.SetupCompatServiceInvoker; 25 import com.google.android.setupcompat.logging.internal.MetricBundleConverter; 26 import com.google.android.setupcompat.logging.internal.SetupMetricsLoggingConstants.MetricType; 27 import com.google.android.setupcompat.util.Logger; 28 import java.util.concurrent.TimeUnit; 29 30 /** 31 * SetupMetricsLogger provides an easy way to log custom metrics to SetupWizard. 32 * (go/suw-metrics-collection-api) 33 */ 34 public class SetupMetricsLogger { 35 36 private static final Logger LOG = new Logger("SetupMetricsLogger"); 37 38 /** Logs an instance of {@link CustomEvent} to SetupWizard. */ logCustomEvent(@onNull Context context, @NonNull CustomEvent customEvent)39 public static void logCustomEvent(@NonNull Context context, @NonNull CustomEvent customEvent) { 40 Preconditions.checkNotNull(context, "Context cannot be null."); 41 Preconditions.checkNotNull(customEvent, "CustomEvent cannot be null."); 42 SetupCompatServiceInvoker.get(context) 43 .logMetricEvent( 44 MetricType.CUSTOM_EVENT, MetricBundleConverter.createBundleForLogging(customEvent)); 45 } 46 47 /** Increments the counter value with the name {@code counterName} by {@code times}. */ logCounter( @onNull Context context, @NonNull MetricKey counterName, int times)48 public static void logCounter( 49 @NonNull Context context, @NonNull MetricKey counterName, int times) { 50 Preconditions.checkNotNull(context, "Context cannot be null."); 51 Preconditions.checkNotNull(counterName, "CounterName cannot be null."); 52 Preconditions.checkArgument(times > 0, "Counter cannot be negative."); 53 SetupCompatServiceInvoker.get(context) 54 .logMetricEvent( 55 MetricType.COUNTER_EVENT, 56 MetricBundleConverter.createBundleForLoggingCounter(counterName, times)); 57 } 58 59 /** 60 * Logs the {@link Timer}'s duration by calling {@link #logDuration(Context, MetricKey, long)}. 61 */ logDuration(@onNull Context context, @NonNull Timer timer)62 public static void logDuration(@NonNull Context context, @NonNull Timer timer) { 63 Preconditions.checkNotNull(context, "Context cannot be null."); 64 Preconditions.checkNotNull(timer, "Timer cannot be null."); 65 Preconditions.checkArgument( 66 timer.isStopped(), "Timer should be stopped before calling logDuration."); 67 logDuration( 68 context, timer.getMetricKey(), TimeUnit.NANOSECONDS.toMillis(timer.getDurationInNanos())); 69 } 70 71 /** Logs a duration event to SetupWizard. */ logDuration( @onNull Context context, @NonNull MetricKey timerName, long timeInMillis)72 public static void logDuration( 73 @NonNull Context context, @NonNull MetricKey timerName, long timeInMillis) { 74 Preconditions.checkNotNull(context, "Context cannot be null."); 75 Preconditions.checkNotNull(timerName, "Timer name cannot be null."); 76 Preconditions.checkArgument(timeInMillis >= 0, "Duration cannot be negative."); 77 SetupCompatServiceInvoker.get(context) 78 .logMetricEvent( 79 MetricType.DURATION_EVENT, 80 MetricBundleConverter.createBundleForLoggingTimer(timerName, timeInMillis)); 81 } 82 83 /** 84 * Logs setup collection metrics 85 */ logMetrics( @onNull Context context, @NonNull ScreenKey screenKey, @NonNull SetupMetric... metrics)86 public static void logMetrics( 87 @NonNull Context context, @NonNull ScreenKey screenKey, @NonNull SetupMetric... metrics) { 88 Preconditions.checkNotNull(context, "Context cannot be null."); 89 Preconditions.checkNotNull(screenKey, "ScreenKey cannot be null."); 90 Preconditions.checkNotNull(metrics, "SetupMetric cannot be null."); 91 92 for (SetupMetric metric : metrics) { 93 LOG.atDebug("Log metric: " + screenKey + ", " + metric); 94 95 SetupCompatServiceInvoker.get(context).logMetricEvent( 96 MetricType.SETUP_COLLECTION_EVENT, 97 MetricBundleConverter.createBundleForLoggingSetupMetric(screenKey, metric)); 98 } 99 } 100 101 /** 102 * A non-static method to log setup collection metrics calling 103 * {@link #logMetrics(Context, ScreenKey, SetupMetric...)} as the actual implementation. This 104 * function is useful when performing unit tests in caller's implementation. 105 * <p> 106 * For unit testing, caller uses {@link #setInstanceForTesting(SetupMetricsLogger)} to inject the 107 * mocked SetupMetricsLogger instance and use {@link SetupMetricsLogger#get(Context)} to get the 108 * SetupMetricsLogger. And verify the this function is called with expected parameters. 109 * 110 * @see #logMetrics(Context, ScreenKey, SetupMetric...) 111 */ logMetrics(@onNull ScreenKey screenKey, @NonNull SetupMetric... metrics)112 public void logMetrics(@NonNull ScreenKey screenKey, @NonNull SetupMetric... metrics) { 113 SetupMetricsLogger.logMetrics(context, screenKey, metrics); 114 } 115 SetupMetricsLogger(Context context)116 private SetupMetricsLogger(Context context) { 117 this.context = context; 118 } 119 120 private final Context context; 121 122 /** Use this function to get a singleton of {@link SetupMetricsLogger} */ get(Context context)123 public static synchronized SetupMetricsLogger get(Context context) { 124 if (instance == null) { 125 instance = new SetupMetricsLogger(context.getApplicationContext()); 126 } 127 128 return instance; 129 } 130 131 @VisibleForTesting setInstanceForTesting(SetupMetricsLogger testInstance)132 public static void setInstanceForTesting(SetupMetricsLogger testInstance) { 133 instance = testInstance; 134 } 135 136 // The instance is coming from Application context which alive during the application activate and 137 // it's not depend on the activities life cycle, so we can avoid memory leak. However linter 138 // cannot distinguish Application context or activity context, so we add @SuppressLint to avoid 139 // lint error. 140 @SuppressLint("StaticFieldLeak") 141 private static SetupMetricsLogger instance; 142 } 143