1 /* 2 * Copyright (C) 2016 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.android.settingslib.core.instrumentation; 17 18 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE; 19 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_FLOAT_VALUE; 20 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE; 21 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME; 22 import static org.mockito.Matchers.any; 23 import static org.mockito.Matchers.anyInt; 24 import static org.mockito.Matchers.argThat; 25 import static org.mockito.Matchers.eq; 26 import static org.mockito.Mockito.times; 27 import static org.mockito.Mockito.verify; 28 29 import android.content.Context; 30 import android.content.SharedPreferences; 31 import android.util.Pair; 32 33 import com.android.settingslib.SettingsLibRobolectricTestRunner; 34 35 import org.junit.Before; 36 import org.junit.Test; 37 import org.junit.runner.RunWith; 38 import org.mockito.Answers; 39 import org.mockito.ArgumentMatcher; 40 import org.mockito.Mock; 41 import org.mockito.MockitoAnnotations; 42 43 @RunWith(SettingsLibRobolectricTestRunner.class) 44 public class SharedPreferenceLoggerTest { 45 46 private static final String TEST_TAG = "tag"; 47 private static final String TEST_KEY = "key"; 48 49 @Mock(answer = Answers.RETURNS_DEEP_STUBS) 50 private Context mContext; 51 52 private ArgumentMatcher<Pair<Integer, Object>> mNamePairMatcher; 53 @Mock 54 private MetricsFeatureProvider mMetricsFeature; 55 private SharedPreferencesLogger mSharedPrefLogger; 56 57 @Before 58 public void init() { 59 MockitoAnnotations.initMocks(this); 60 mSharedPrefLogger = new SharedPreferencesLogger(mContext, TEST_TAG, mMetricsFeature); 61 mNamePairMatcher = pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_NAME, String.class); 62 } 63 64 @Test 65 public void putInt_shouldNotLogInitialPut() { 66 final SharedPreferences.Editor editor = mSharedPrefLogger.edit(); 67 editor.putInt(TEST_KEY, 1); 68 editor.putInt(TEST_KEY, 1); 69 editor.putInt(TEST_KEY, 1); 70 editor.putInt(TEST_KEY, 2); 71 editor.putInt(TEST_KEY, 2); 72 editor.putInt(TEST_KEY, 2); 73 editor.putInt(TEST_KEY, 2); 74 75 verify(mMetricsFeature, times(6)).action(any(Context.class), anyInt(), 76 argThat(mNamePairMatcher), 77 argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, Integer.class))); 78 } 79 80 @Test 81 public void putBoolean_shouldNotLogInitialPut() { 82 final SharedPreferences.Editor editor = mSharedPrefLogger.edit(); 83 editor.putBoolean(TEST_KEY, true); 84 editor.putBoolean(TEST_KEY, true); 85 editor.putBoolean(TEST_KEY, false); 86 editor.putBoolean(TEST_KEY, false); 87 editor.putBoolean(TEST_KEY, false); 88 89 90 verify(mMetricsFeature).action(any(Context.class), anyInt(), 91 argThat(mNamePairMatcher), 92 argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, true))); 93 verify(mMetricsFeature, times(3)).action(any(Context.class), anyInt(), 94 argThat(mNamePairMatcher), 95 argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, false))); 96 } 97 98 @Test 99 public void putLong_shouldNotLogInitialPut() { 100 final SharedPreferences.Editor editor = mSharedPrefLogger.edit(); 101 editor.putLong(TEST_KEY, 1); 102 editor.putLong(TEST_KEY, 1); 103 editor.putLong(TEST_KEY, 1); 104 editor.putLong(TEST_KEY, 1); 105 editor.putLong(TEST_KEY, 2); 106 107 verify(mMetricsFeature, times(4)).action(any(Context.class), anyInt(), 108 argThat(mNamePairMatcher), 109 argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, Integer.class))); 110 } 111 112 @Test 113 public void putLong_biggerThanIntMax_shouldLogIntMax() { 114 final SharedPreferences.Editor editor = mSharedPrefLogger.edit(); 115 final long veryBigNumber = 500L + Integer.MAX_VALUE; 116 editor.putLong(TEST_KEY, 1); 117 editor.putLong(TEST_KEY, veryBigNumber); 118 119 verify(mMetricsFeature).action(any(Context.class), anyInt(), 120 argThat(mNamePairMatcher), 121 argThat(pairMatches( 122 FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, Integer.MAX_VALUE))); 123 } 124 125 @Test 126 public void putLong_smallerThanIntMin_shouldLogIntMin() { 127 final SharedPreferences.Editor editor = mSharedPrefLogger.edit(); 128 final long veryNegativeNumber = -500L + Integer.MIN_VALUE; 129 editor.putLong(TEST_KEY, 1); 130 editor.putLong(TEST_KEY, veryNegativeNumber); 131 132 verify(mMetricsFeature).action(any(Context.class), anyInt(), 133 argThat(mNamePairMatcher), 134 argThat(pairMatches( 135 FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, Integer.MIN_VALUE))); 136 } 137 138 @Test 139 public void putFloat_shouldNotLogInitialPut() { 140 final SharedPreferences.Editor editor = mSharedPrefLogger.edit(); 141 editor.putFloat(TEST_KEY, 1); 142 editor.putFloat(TEST_KEY, 1); 143 editor.putFloat(TEST_KEY, 1); 144 editor.putFloat(TEST_KEY, 1); 145 editor.putFloat(TEST_KEY, 2); 146 147 verify(mMetricsFeature, times(4)).action(any(Context.class), anyInt(), 148 argThat(mNamePairMatcher), 149 argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_FLOAT_VALUE, Float.class))); 150 } 151 152 @Test 153 public void logPackage_shouldUseLogPackageApi() { 154 mSharedPrefLogger.logPackageName("key", "com.android.settings"); 155 verify(mMetricsFeature).action(any(Context.class), 156 eq(ACTION_SETTINGS_PREFERENCE_CHANGE), 157 eq("com.android.settings"), 158 any(Pair.class)); 159 } 160 161 private ArgumentMatcher<Pair<Integer, Object>> pairMatches(int tag, Class clazz) { 162 return pair -> pair.first == tag && isInstanceOfType(pair.second, clazz); 163 } 164 165 private ArgumentMatcher<Pair<Integer, Object>> pairMatches(int tag, boolean bool) { 166 return pair -> pair.first == tag 167 && isInstanceOfType(pair.second, Integer.class) 168 && pair.second.equals((bool ? 1 : 0)); 169 } 170 171 private ArgumentMatcher<Pair<Integer, Object>> pairMatches(int tag, int val) { 172 return pair -> pair.first == tag 173 && isInstanceOfType(pair.second, Integer.class) 174 && pair.second.equals(val); 175 } 176 177 /** Returns true if the instance is assignable to the type Clazz. */ 178 private static boolean isInstanceOfType(Object instance, Class<?> clazz) { 179 return clazz.isInstance(instance); 180 } 181 } 182