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.android.settings.fuelgauge.batterytip; 18 19 import static android.os.StatsDimensionsValue.FLOAT_VALUE_TYPE; 20 import static android.os.StatsDimensionsValue.INT_VALUE_TYPE; 21 import static android.os.StatsDimensionsValue.TUPLE_VALUE_TYPE; 22 23 import static com.google.common.truth.Truth.assertThat; 24 25 import static org.mockito.ArgumentMatchers.any; 26 import static org.mockito.ArgumentMatchers.anyBoolean; 27 import static org.mockito.ArgumentMatchers.anyInt; 28 import static org.mockito.ArgumentMatchers.anyLong; 29 import static org.mockito.ArgumentMatchers.anyString; 30 import static org.mockito.ArgumentMatchers.eq; 31 import static org.mockito.Mockito.doNothing; 32 import static org.mockito.Mockito.doReturn; 33 import static org.mockito.Mockito.doThrow; 34 import static org.mockito.Mockito.mock; 35 import static org.mockito.Mockito.never; 36 import static org.mockito.Mockito.spy; 37 import static org.mockito.Mockito.verify; 38 import static org.mockito.Mockito.when; 39 40 import android.app.JobSchedulerImpl; 41 import android.app.StatsManager; 42 import android.app.job.IJobScheduler; 43 import android.app.job.JobInfo; 44 import android.app.job.JobParameters; 45 import android.app.job.JobScheduler; 46 import android.app.job.JobWorkItem; 47 import android.app.settings.SettingsEnums; 48 import android.content.Context; 49 import android.content.Intent; 50 import android.os.Binder; 51 import android.os.Bundle; 52 import android.os.Process; 53 import android.os.StatsDimensionsValue; 54 import android.os.UserManager; 55 56 import com.android.internal.logging.nano.MetricsProto; 57 import com.android.settings.R; 58 import com.android.settings.fuelgauge.BatteryUtils; 59 import com.android.settings.testutils.FakeFeatureFactory; 60 import com.android.settings.testutils.shadow.ShadowConnectivityManager; 61 import com.android.settingslib.fuelgauge.PowerAllowlistBackend; 62 63 import org.junit.Before; 64 import org.junit.Test; 65 import org.junit.runner.RunWith; 66 import org.mockito.Mock; 67 import org.mockito.MockitoAnnotations; 68 import org.robolectric.Robolectric; 69 import org.robolectric.RobolectricTestRunner; 70 import org.robolectric.RuntimeEnvironment; 71 import org.robolectric.android.controller.ServiceController; 72 import org.robolectric.annotation.Config; 73 74 import java.util.ArrayList; 75 import java.util.List; 76 import java.util.concurrent.TimeUnit; 77 78 @RunWith(RobolectricTestRunner.class) 79 @Config(shadows = {ShadowConnectivityManager.class}) 80 public class AnomalyDetectionJobServiceTest { 81 private static final int UID = 12345; 82 private static final String SYSTEM_PACKAGE = "com.android.system"; 83 private static final String SUBSCRIBER_COOKIES_AUTO_RESTRICTION = 84 "anomaly_type=6,auto_restriction=true"; 85 private static final String SUBSCRIBER_COOKIES_NOT_AUTO_RESTRICTION = 86 "anomaly_type=6,auto_restriction=false"; 87 private static final int ANOMALY_TYPE = 6; 88 private static final long VERSION_CODE = 15; 89 @Mock 90 private UserManager mUserManager; 91 @Mock 92 private BatteryDatabaseManager mBatteryDatabaseManager; 93 @Mock 94 private BatteryUtils mBatteryUtils; 95 @Mock 96 private PowerAllowlistBackend mPowerAllowlistBackend; 97 @Mock 98 private StatsDimensionsValue mStatsDimensionsValue; 99 @Mock 100 private JobParameters mJobParameters; 101 @Mock 102 private JobWorkItem mJobWorkItem; 103 104 private BatteryTipPolicy mPolicy; 105 private Bundle mBundle; 106 private AnomalyDetectionJobService mAnomalyDetectionJobService; 107 private FakeFeatureFactory mFeatureFactory; 108 private Context mContext; 109 private JobScheduler mJobScheduler; 110 111 @Before setUp()112 public void setUp() { 113 MockitoAnnotations.initMocks(this); 114 115 mContext = spy(RuntimeEnvironment.application); 116 mJobScheduler = spy(new JobSchedulerImpl(IJobScheduler.Stub.asInterface(new Binder()))); 117 when(mContext.getSystemService(JobScheduler.class)).thenReturn(mJobScheduler); 118 119 mPolicy = new BatteryTipPolicy(mContext); 120 mBundle = new Bundle(); 121 mBundle.putParcelable(StatsManager.EXTRA_STATS_DIMENSIONS_VALUE, mStatsDimensionsValue); 122 mFeatureFactory = FakeFeatureFactory.setupForTest(); 123 when(mBatteryUtils.getAppLongVersionCode(any())).thenReturn(VERSION_CODE); 124 125 final ServiceController<AnomalyDetectionJobService> controller = 126 Robolectric.buildService(AnomalyDetectionJobService.class); 127 mAnomalyDetectionJobService = spy(controller.get()); 128 doNothing().when(mAnomalyDetectionJobService).jobFinished(any(), anyBoolean()); 129 } 130 131 @Test scheduleCleanUp()132 public void scheduleCleanUp() { 133 AnomalyDetectionJobService.scheduleAnomalyDetection(mContext, new Intent()); 134 135 JobScheduler jobScheduler = mContext.getSystemService(JobScheduler.class); 136 List<JobInfo> pendingJobs = jobScheduler.getAllPendingJobs(); 137 assertThat(pendingJobs).hasSize(1); 138 139 JobInfo pendingJob = pendingJobs.get(0); 140 assertThat(pendingJob.getId()).isEqualTo(R.integer.job_anomaly_detection); 141 assertThat(pendingJob.getMaxExecutionDelayMillis()) 142 .isEqualTo(TimeUnit.MINUTES.toMillis(30)); 143 } 144 145 @Test saveAnomalyToDatabase_systemAllowlisted_doNotSave()146 public void saveAnomalyToDatabase_systemAllowlisted_doNotSave() { 147 doReturn(UID).when(mAnomalyDetectionJobService).extractUidFromStatsDimensionsValue(any()); 148 doReturn(true).when(mPowerAllowlistBackend).isAllowlisted(any(String[].class)); 149 150 mAnomalyDetectionJobService.saveAnomalyToDatabase(mContext, 151 mUserManager, mBatteryDatabaseManager, mBatteryUtils, mPolicy, 152 mPowerAllowlistBackend, mContext.getContentResolver(), 153 mFeatureFactory.powerUsageFeatureProvider, 154 mFeatureFactory.metricsFeatureProvider, mBundle); 155 156 verify(mBatteryDatabaseManager, never()).insertAnomaly(anyInt(), anyString(), anyInt(), 157 anyInt(), anyLong()); 158 } 159 160 @Test saveAnomalyToDatabase_systemApp_doNotSaveButLog()161 public void saveAnomalyToDatabase_systemApp_doNotSaveButLog() { 162 final ArrayList<String> cookies = new ArrayList<>(); 163 cookies.add(SUBSCRIBER_COOKIES_AUTO_RESTRICTION); 164 mBundle.putStringArrayList(StatsManager.EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES, cookies); 165 doReturn(SYSTEM_PACKAGE).when(mBatteryUtils).getPackageName(anyInt()); 166 doReturn(false).when(mPowerAllowlistBackend).isSysAllowlisted(SYSTEM_PACKAGE); 167 doReturn(Process.FIRST_APPLICATION_UID).when( 168 mAnomalyDetectionJobService).extractUidFromStatsDimensionsValue(any()); 169 doReturn(true).when(mBatteryUtils).shouldHideAnomaly(any(), anyInt(), any()); 170 171 mAnomalyDetectionJobService.saveAnomalyToDatabase(mContext, 172 mUserManager, mBatteryDatabaseManager, mBatteryUtils, mPolicy, 173 mPowerAllowlistBackend, mContext.getContentResolver(), 174 mFeatureFactory.powerUsageFeatureProvider, 175 mFeatureFactory.metricsFeatureProvider, mBundle); 176 177 verify(mBatteryDatabaseManager, never()).insertAnomaly(anyInt(), anyString(), anyInt(), 178 anyInt(), anyLong()); 179 verify(mFeatureFactory.metricsFeatureProvider).action(SettingsEnums.PAGE_UNKNOWN, 180 MetricsProto.MetricsEvent.ACTION_ANOMALY_IGNORED, 181 SettingsEnums.PAGE_UNKNOWN, 182 SYSTEM_PACKAGE + "/" + VERSION_CODE, 183 ANOMALY_TYPE); 184 } 185 186 @Test saveAnomalyToDatabase_systemUid_doNotSave()187 public void saveAnomalyToDatabase_systemUid_doNotSave() { 188 doReturn(Process.SYSTEM_UID).when( 189 mAnomalyDetectionJobService).extractUidFromStatsDimensionsValue(any()); 190 191 mAnomalyDetectionJobService.saveAnomalyToDatabase(mContext, 192 mUserManager, mBatteryDatabaseManager, mBatteryUtils, mPolicy, 193 mPowerAllowlistBackend, mContext.getContentResolver(), 194 mFeatureFactory.powerUsageFeatureProvider, mFeatureFactory.metricsFeatureProvider, 195 mBundle); 196 197 verify(mBatteryDatabaseManager, never()).insertAnomaly(anyInt(), anyString(), anyInt(), 198 anyInt(), anyLong()); 199 } 200 201 @Test saveAnomalyToDatabase_uidNull_doNotSave()202 public void saveAnomalyToDatabase_uidNull_doNotSave() { 203 doReturn(AnomalyDetectionJobService.UID_NULL).when( 204 mAnomalyDetectionJobService).extractUidFromStatsDimensionsValue(any()); 205 206 mAnomalyDetectionJobService.saveAnomalyToDatabase(mContext, 207 mUserManager, mBatteryDatabaseManager, mBatteryUtils, mPolicy, 208 mPowerAllowlistBackend, mContext.getContentResolver(), 209 mFeatureFactory.powerUsageFeatureProvider, mFeatureFactory.metricsFeatureProvider, 210 mBundle); 211 212 verify(mBatteryDatabaseManager, never()).insertAnomaly(anyInt(), anyString(), anyInt(), 213 anyInt(), anyLong()); 214 } 215 216 @Test saveAnomalyToDatabase_normalAppWithAutoRestriction_save()217 public void saveAnomalyToDatabase_normalAppWithAutoRestriction_save() { 218 final ArrayList<String> cookies = new ArrayList<>(); 219 cookies.add(SUBSCRIBER_COOKIES_AUTO_RESTRICTION); 220 mBundle.putStringArrayList(StatsManager.EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES, cookies); 221 doReturn(SYSTEM_PACKAGE).when(mBatteryUtils).getPackageName(anyInt()); 222 doReturn(false).when(mPowerAllowlistBackend).isSysAllowlisted(SYSTEM_PACKAGE); 223 doReturn(Process.FIRST_APPLICATION_UID).when( 224 mAnomalyDetectionJobService).extractUidFromStatsDimensionsValue(any()); 225 226 mAnomalyDetectionJobService.saveAnomalyToDatabase(mContext, 227 mUserManager, mBatteryDatabaseManager, mBatteryUtils, mPolicy, 228 mPowerAllowlistBackend, mContext.getContentResolver(), 229 mFeatureFactory.powerUsageFeatureProvider, mFeatureFactory.metricsFeatureProvider, 230 mBundle); 231 232 verify(mBatteryDatabaseManager).insertAnomaly(anyInt(), anyString(), eq(6), 233 eq(AnomalyDatabaseHelper.State.AUTO_HANDLED), anyLong()); 234 verify(mFeatureFactory.metricsFeatureProvider).action(SettingsEnums.PAGE_UNKNOWN, 235 MetricsProto.MetricsEvent.ACTION_ANOMALY_TRIGGERED, 236 SettingsEnums.PAGE_UNKNOWN, 237 SYSTEM_PACKAGE + "/" + VERSION_CODE, 238 ANOMALY_TYPE); 239 } 240 241 @Test saveAnomalyToDatabase_normalAppWithoutAutoRestriction_save()242 public void saveAnomalyToDatabase_normalAppWithoutAutoRestriction_save() { 243 final ArrayList<String> cookies = new ArrayList<>(); 244 cookies.add(SUBSCRIBER_COOKIES_NOT_AUTO_RESTRICTION); 245 mBundle.putStringArrayList(StatsManager.EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES, cookies); 246 doReturn(SYSTEM_PACKAGE).when(mBatteryUtils).getPackageName(anyInt()); 247 doReturn(false).when(mPowerAllowlistBackend).isSysAllowlisted(SYSTEM_PACKAGE); 248 doReturn(Process.FIRST_APPLICATION_UID).when( 249 mAnomalyDetectionJobService).extractUidFromStatsDimensionsValue(any()); 250 251 mAnomalyDetectionJobService.saveAnomalyToDatabase(mContext, 252 mUserManager, mBatteryDatabaseManager, mBatteryUtils, mPolicy, 253 mPowerAllowlistBackend, mContext.getContentResolver(), 254 mFeatureFactory.powerUsageFeatureProvider, mFeatureFactory.metricsFeatureProvider, 255 mBundle); 256 257 verify(mBatteryDatabaseManager).insertAnomaly(anyInt(), anyString(), eq(6), 258 eq(AnomalyDatabaseHelper.State.NEW), anyLong()); 259 verify(mFeatureFactory.metricsFeatureProvider).action(SettingsEnums.PAGE_UNKNOWN, 260 MetricsProto.MetricsEvent.ACTION_ANOMALY_TRIGGERED, 261 SettingsEnums.PAGE_UNKNOWN, 262 SYSTEM_PACKAGE + "/" + VERSION_CODE, 263 ANOMALY_TYPE); 264 } 265 266 @Test extractUidFromStatsDimensionsValue_extractCorrectUid()267 public void extractUidFromStatsDimensionsValue_extractCorrectUid() { 268 // Build an integer dimensions value. 269 final StatsDimensionsValue intValue = mock(StatsDimensionsValue.class); 270 when(intValue.isValueType(INT_VALUE_TYPE)).thenReturn(true); 271 when(intValue.getField()).thenReturn(AnomalyDetectionJobService.STATSD_UID_FILED); 272 when(intValue.getIntValue()).thenReturn(UID); 273 274 // Build a tuple dimensions value and put the previous integer dimensions value inside. 275 final StatsDimensionsValue tupleValue = mock(StatsDimensionsValue.class); 276 when(tupleValue.isValueType(TUPLE_VALUE_TYPE)).thenReturn(true); 277 final List<StatsDimensionsValue> statsDimensionsValues = new ArrayList<>(); 278 statsDimensionsValues.add(intValue); 279 when(tupleValue.getTupleValueList()).thenReturn(statsDimensionsValues); 280 281 assertThat(mAnomalyDetectionJobService.extractUidFromStatsDimensionsValue( 282 tupleValue)).isEqualTo(UID); 283 } 284 285 @Test extractUidFromStatsDimensionsValue_wrongFormat_returnNull()286 public void extractUidFromStatsDimensionsValue_wrongFormat_returnNull() { 287 // Build a float dimensions value 288 final StatsDimensionsValue floatValue = mock(StatsDimensionsValue.class); 289 when(floatValue.isValueType(FLOAT_VALUE_TYPE)).thenReturn(true); 290 when(floatValue.getField()).thenReturn(AnomalyDetectionJobService.STATSD_UID_FILED); 291 when(floatValue.getFloatValue()).thenReturn(0f); 292 293 assertThat(mAnomalyDetectionJobService.extractUidFromStatsDimensionsValue( 294 floatValue)).isEqualTo(AnomalyDetectionJobService.UID_NULL); 295 } 296 297 @Test stopJobWhileDequeuingWork_shouldNotCrash()298 public void stopJobWhileDequeuingWork_shouldNotCrash() { 299 when(mJobParameters.dequeueWork()).thenThrow(new SecurityException()); 300 301 mAnomalyDetectionJobService.onStopJob(mJobParameters); 302 303 // Should not crash even job is stopped 304 mAnomalyDetectionJobService.dequeueWork(mJobParameters); 305 } 306 307 @Test stopJobWhileCompletingWork_shouldNotCrash()308 public void stopJobWhileCompletingWork_shouldNotCrash() { 309 doThrow(new SecurityException()).when(mJobParameters).completeWork(any()); 310 311 mAnomalyDetectionJobService.onStopJob(mJobParameters); 312 313 // Should not crash even job is stopped 314 mAnomalyDetectionJobService.completeWork(mJobParameters, mJobWorkItem); 315 } 316 317 @Test restartWorkAfterBeenStopped_jobStarted()318 public void restartWorkAfterBeenStopped_jobStarted() { 319 mAnomalyDetectionJobService.onStopJob(mJobParameters); 320 mAnomalyDetectionJobService.onStartJob(mJobParameters); 321 322 assertThat(mAnomalyDetectionJobService.mIsJobCanceled).isFalse(); 323 } 324 } 325