1 /* 2 * Copyright (C) 2022 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.android.internal.util; 18 19 import static android.text.TextUtils.formatSimple; 20 21 import static com.android.internal.util.LatencyTracker.STATSD_ACTION; 22 23 import static com.google.common.truth.Truth.assertThat; 24 import static com.google.common.truth.Truth.assertWithMessage; 25 26 import android.provider.DeviceConfig; 27 import android.util.Log; 28 29 import androidx.test.ext.junit.runners.AndroidJUnit4; 30 import androidx.test.filters.SmallTest; 31 32 import com.google.common.truth.Expect; 33 34 import org.junit.Before; 35 import org.junit.Rule; 36 import org.junit.Test; 37 import org.junit.runner.RunWith; 38 39 import java.lang.reflect.Field; 40 import java.lang.reflect.Modifier; 41 import java.time.Duration; 42 import java.util.Arrays; 43 import java.util.HashMap; 44 import java.util.HashSet; 45 import java.util.List; 46 import java.util.Map; 47 import java.util.stream.Collectors; 48 49 @SmallTest 50 @RunWith(AndroidJUnit4.class) 51 public class LatencyTrackerTest { 52 private static final String TAG = LatencyTrackerTest.class.getSimpleName(); 53 private static final String ENUM_NAME_PREFIX = "UIACTION_LATENCY_REPORTED__ACTION__"; 54 private static final String ACTION_ENABLE_SUFFIX = "_enable"; 55 private static final Duration TEST_TIMEOUT = Duration.ofMillis(500); 56 57 @Rule 58 public final Expect mExpect = Expect.create(); 59 60 @Before setUp()61 public void setUp() { 62 DeviceConfig.deleteProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER, 63 LatencyTracker.SETTINGS_ENABLED_KEY); 64 getAllActions().forEach(action -> { 65 DeviceConfig.deleteProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER, 66 action.getName().toLowerCase() + ACTION_ENABLE_SUFFIX); 67 }); 68 } 69 70 @Test testCujsMapToEnumsCorrectly()71 public void testCujsMapToEnumsCorrectly() { 72 List<Field> actions = getAllActions(); 73 Map<Integer, String> enumsMap = Arrays.stream(FrameworkStatsLog.class.getDeclaredFields()) 74 .filter(f -> f.getName().startsWith(ENUM_NAME_PREFIX) 75 && Modifier.isStatic(f.getModifiers()) 76 && f.getType() == int.class) 77 .collect(Collectors.toMap(this::getIntFieldChecked, Field::getName)); 78 79 assertThat(enumsMap.size() - 1).isEqualTo(actions.size()); 80 81 actions.forEach(f -> { 82 final int action = getIntFieldChecked(f); 83 final String actionName = f.getName(); 84 final String expectedEnumName = formatSimple("%s%s", ENUM_NAME_PREFIX, actionName); 85 final int enumKey = STATSD_ACTION[action]; 86 final String enumName = enumsMap.get(enumKey); 87 final String expectedActionName = LatencyTracker.getNameOfAction(enumKey); 88 mExpect 89 .withMessage(formatSimple( 90 "%s (%d) not matches %s (%d)", actionName, action, enumName, enumKey)) 91 .that(expectedEnumName.equals(enumName)) 92 .isTrue(); 93 mExpect 94 .withMessage( 95 formatSimple("getNameOfAction(%d) not matches: %s, expected=%s", 96 enumKey, actionName, expectedActionName)) 97 .that(actionName.equals(expectedActionName)) 98 .isTrue(); 99 }); 100 } 101 102 @Test testCujTypeEnumCorrectlyDefined()103 public void testCujTypeEnumCorrectlyDefined() throws Exception { 104 List<Field> cujEnumFields = getAllActions(); 105 HashSet<Integer> allValues = new HashSet<>(); 106 for (Field field : cujEnumFields) { 107 int fieldValue = field.getInt(null); 108 assertWithMessage( 109 "Field %s must have a mapping to a value in STATSD_ACTION", 110 field.getName()) 111 .that(fieldValue < STATSD_ACTION.length) 112 .isTrue(); 113 assertWithMessage("All CujType values must be unique. Field %s repeats existing value.", 114 field.getName()) 115 .that(allValues.add(fieldValue)) 116 .isTrue(); 117 } 118 } 119 120 @Test 121 public void testIsEnabled_globalEnabled() { 122 DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER, 123 LatencyTracker.SETTINGS_ENABLED_KEY, "true", false); 124 LatencyTracker latencyTracker = new LatencyTracker(); 125 waitForLatencyTrackerToUpdateProperties(latencyTracker); 126 assertThat(latencyTracker.isEnabled()).isTrue(); 127 } 128 129 @Test 130 public void testIsEnabled_globalDisabled() { 131 DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER, 132 LatencyTracker.SETTINGS_ENABLED_KEY, "false", false); 133 LatencyTracker latencyTracker = new LatencyTracker(); 134 waitForLatencyTrackerToUpdateProperties(latencyTracker); 135 assertThat(latencyTracker.isEnabled()).isFalse(); 136 } 137 138 @Test 139 public void testIsEnabledAction_useGlobalValueWhenActionEnableIsNotSet() { 140 LatencyTracker latencyTracker = new LatencyTracker(); 141 // using a single test action, but this applies to all actions 142 int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION; 143 Log.i(TAG, "setting property=" + LatencyTracker.SETTINGS_ENABLED_KEY + ", value=true"); 144 latencyTracker.mDeviceConfigPropertiesUpdated.close(); 145 DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER, 146 LatencyTracker.SETTINGS_ENABLED_KEY, "true", false); 147 waitForLatencyTrackerToUpdateProperties(latencyTracker); 148 assertThat( 149 latencyTracker.isEnabled(action)).isTrue(); 150 151 Log.i(TAG, "setting property=" + LatencyTracker.SETTINGS_ENABLED_KEY 152 + ", value=false"); 153 latencyTracker.mDeviceConfigPropertiesUpdated.close(); 154 DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER, 155 LatencyTracker.SETTINGS_ENABLED_KEY, "false", false); 156 waitForLatencyTrackerToUpdateProperties(latencyTracker); 157 assertThat(latencyTracker.isEnabled(action)).isFalse(); 158 } 159 160 @Test 161 public void testIsEnabledAction_actionPropertyOverridesGlobalProperty() 162 throws DeviceConfig.BadConfigException { 163 LatencyTracker latencyTracker = new LatencyTracker(); 164 // using a single test action, but this applies to all actions 165 int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION; 166 String actionEnableProperty = "action_show_voice_interaction" + ACTION_ENABLE_SUFFIX; 167 Log.i(TAG, "setting property=" + actionEnableProperty + ", value=true"); 168 169 latencyTracker.mDeviceConfigPropertiesUpdated.close(); 170 Map<String, String> properties = new HashMap<String, String>() {{ 171 put(LatencyTracker.SETTINGS_ENABLED_KEY, "false"); 172 put(actionEnableProperty, "true"); 173 }}; 174 DeviceConfig.setProperties( 175 new DeviceConfig.Properties(DeviceConfig.NAMESPACE_LATENCY_TRACKER, 176 properties)); 177 waitForLatencyTrackerToUpdateProperties(latencyTracker); 178 assertThat(latencyTracker.isEnabled(action)).isTrue(); 179 180 latencyTracker.mDeviceConfigPropertiesUpdated.close(); 181 Log.i(TAG, "setting property=" + actionEnableProperty + ", value=false"); 182 properties.put(LatencyTracker.SETTINGS_ENABLED_KEY, "true"); 183 properties.put(actionEnableProperty, "false"); 184 DeviceConfig.setProperties( 185 new DeviceConfig.Properties(DeviceConfig.NAMESPACE_LATENCY_TRACKER, 186 properties)); 187 waitForLatencyTrackerToUpdateProperties(latencyTracker); 188 assertThat(latencyTracker.isEnabled(action)).isFalse(); 189 } 190 191 private void waitForLatencyTrackerToUpdateProperties(LatencyTracker latencyTracker) { 192 try { 193 Thread.sleep(TEST_TIMEOUT.toMillis()); 194 } catch (InterruptedException e) { 195 e.printStackTrace(); 196 } 197 assertThat(latencyTracker.mDeviceConfigPropertiesUpdated.block( 198 TEST_TIMEOUT.toMillis())).isTrue(); 199 } 200 201 private List<Field> getAllActions() { 202 return Arrays.stream(LatencyTracker.class.getDeclaredFields()) 203 .filter(field -> field.getName().startsWith("ACTION_") 204 && Modifier.isStatic(field.getModifiers()) 205 && field.getType() == int.class) 206 .collect(Collectors.toList()); 207 } 208 getIntFieldChecked(Field field)209 private int getIntFieldChecked(Field field) { 210 try { 211 return field.getInt(null); 212 } catch (IllegalAccessException ex) { 213 throw new RuntimeException(ex); 214 } 215 } 216 } 217