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(mContext, 117 IJobScheduler.Stub.asInterface(new Binder()))); 118 when(mContext.getSystemService(JobScheduler.class)).thenReturn(mJobScheduler); 119 120 mPolicy = new BatteryTipPolicy(mContext); 121 mBundle = new Bundle(); 122 mBundle.putParcelable(StatsManager.EXTRA_STATS_DIMENSIONS_VALUE, mStatsDimensionsValue); 123 mFeatureFactory = FakeFeatureFactory.setupForTest(); 124 when(mBatteryUtils.getAppLongVersionCode(any())).thenReturn(VERSION_CODE); 125 126 final ServiceController<AnomalyDetectionJobService> controller = 127 Robolectric.buildService(AnomalyDetectionJobService.class); 128 mAnomalyDetectionJobService = spy(controller.get()); 129 doNothing().when(mAnomalyDetectionJobService).jobFinished(any(), anyBoolean()); 130 } 131 132 @Test scheduleCleanUp()133 public void scheduleCleanUp() { 134 AnomalyDetectionJobService.scheduleAnomalyDetection(mContext, new Intent()); 135 136 JobScheduler jobScheduler = mContext.getSystemService(JobScheduler.class); 137 List<JobInfo> pendingJobs = jobScheduler.getAllPendingJobs(); 138 assertThat(pendingJobs).hasSize(1); 139 140 JobInfo pendingJob = pendingJobs.get(0); 141 assertThat(pendingJob.getId()).isEqualTo(R.integer.job_anomaly_detection); 142 assertThat(pendingJob.getMaxExecutionDelayMillis()) 143 .isEqualTo(TimeUnit.MINUTES.toMillis(30)); 144 } 145 146 @Test saveAnomalyToDatabase_systemAllowlisted_doNotSave()147 public void saveAnomalyToDatabase_systemAllowlisted_doNotSave() { 148 doReturn(UID).when(mAnomalyDetectionJobService).extractUidFromStatsDimensionsValue(any()); 149 doReturn(true).when(mPowerAllowlistBackend) 150 .isAllowlisted(any(String[].class), any(Integer.class)); 151 152 mAnomalyDetectionJobService.saveAnomalyToDatabase(mContext, 153 mUserManager, mBatteryDatabaseManager, mBatteryUtils, mPolicy, 154 mPowerAllowlistBackend, mContext.getContentResolver(), 155 mFeatureFactory.powerUsageFeatureProvider, 156 mFeatureFactory.metricsFeatureProvider, mBundle); 157 158 verify(mBatteryDatabaseManager, never()).insertAnomaly(anyInt(), anyString(), anyInt(), 159 anyInt(), anyLong()); 160 } 161 162 @Test saveAnomalyToDatabase_systemApp_doNotSaveButLog()163 public void saveAnomalyToDatabase_systemApp_doNotSaveButLog() { 164 final ArrayList<String> cookies = new ArrayList<>(); 165 cookies.add(SUBSCRIBER_COOKIES_AUTO_RESTRICTION); 166 mBundle.putStringArrayList(StatsManager.EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES, cookies); 167 doReturn(SYSTEM_PACKAGE).when(mBatteryUtils).getPackageName(anyInt()); 168 doReturn(false).when(mPowerAllowlistBackend).isSysAllowlisted(SYSTEM_PACKAGE); 169 doReturn(Process.FIRST_APPLICATION_UID).when( 170 mAnomalyDetectionJobService).extractUidFromStatsDimensionsValue(any()); 171 doReturn(true).when(mBatteryUtils).shouldHideAnomaly(any(), anyInt(), any()); 172 173 mAnomalyDetectionJobService.saveAnomalyToDatabase(mContext, 174 mUserManager, mBatteryDatabaseManager, mBatteryUtils, mPolicy, 175 mPowerAllowlistBackend, mContext.getContentResolver(), 176 mFeatureFactory.powerUsageFeatureProvider, 177 mFeatureFactory.metricsFeatureProvider, mBundle); 178 179 verify(mBatteryDatabaseManager, never()).insertAnomaly(anyInt(), anyString(), anyInt(), 180 anyInt(), anyLong()); 181 verify(mFeatureFactory.metricsFeatureProvider).action(SettingsEnums.PAGE_UNKNOWN, 182 MetricsProto.MetricsEvent.ACTION_ANOMALY_IGNORED, 183 SettingsEnums.PAGE_UNKNOWN, 184 SYSTEM_PACKAGE + "/" + VERSION_CODE, 185 ANOMALY_TYPE); 186 } 187 188 @Test saveAnomalyToDatabase_systemUid_doNotSave()189 public void saveAnomalyToDatabase_systemUid_doNotSave() { 190 doReturn(Process.SYSTEM_UID).when( 191 mAnomalyDetectionJobService).extractUidFromStatsDimensionsValue(any()); 192 193 mAnomalyDetectionJobService.saveAnomalyToDatabase(mContext, 194 mUserManager, mBatteryDatabaseManager, mBatteryUtils, mPolicy, 195 mPowerAllowlistBackend, mContext.getContentResolver(), 196 mFeatureFactory.powerUsageFeatureProvider, mFeatureFactory.metricsFeatureProvider, 197 mBundle); 198 199 verify(mBatteryDatabaseManager, never()).insertAnomaly(anyInt(), anyString(), anyInt(), 200 anyInt(), anyLong()); 201 } 202 203 @Test saveAnomalyToDatabase_uidNull_doNotSave()204 public void saveAnomalyToDatabase_uidNull_doNotSave() { 205 doReturn(AnomalyDetectionJobService.UID_NULL).when( 206 mAnomalyDetectionJobService).extractUidFromStatsDimensionsValue(any()); 207 208 mAnomalyDetectionJobService.saveAnomalyToDatabase(mContext, 209 mUserManager, mBatteryDatabaseManager, mBatteryUtils, mPolicy, 210 mPowerAllowlistBackend, mContext.getContentResolver(), 211 mFeatureFactory.powerUsageFeatureProvider, mFeatureFactory.metricsFeatureProvider, 212 mBundle); 213 214 verify(mBatteryDatabaseManager, never()).insertAnomaly(anyInt(), anyString(), anyInt(), 215 anyInt(), anyLong()); 216 } 217 218 @Test saveAnomalyToDatabase_normalAppWithAutoRestriction_save()219 public void saveAnomalyToDatabase_normalAppWithAutoRestriction_save() { 220 final ArrayList<String> cookies = new ArrayList<>(); 221 cookies.add(SUBSCRIBER_COOKIES_AUTO_RESTRICTION); 222 mBundle.putStringArrayList(StatsManager.EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES, cookies); 223 doReturn(SYSTEM_PACKAGE).when(mBatteryUtils).getPackageName(anyInt()); 224 doReturn(false).when(mPowerAllowlistBackend).isSysAllowlisted(SYSTEM_PACKAGE); 225 doReturn(Process.FIRST_APPLICATION_UID).when( 226 mAnomalyDetectionJobService).extractUidFromStatsDimensionsValue(any()); 227 228 mAnomalyDetectionJobService.saveAnomalyToDatabase(mContext, 229 mUserManager, mBatteryDatabaseManager, mBatteryUtils, mPolicy, 230 mPowerAllowlistBackend, mContext.getContentResolver(), 231 mFeatureFactory.powerUsageFeatureProvider, mFeatureFactory.metricsFeatureProvider, 232 mBundle); 233 234 verify(mBatteryDatabaseManager).insertAnomaly(anyInt(), anyString(), eq(6), 235 eq(AnomalyDatabaseHelper.State.AUTO_HANDLED), anyLong()); 236 verify(mFeatureFactory.metricsFeatureProvider).action(SettingsEnums.PAGE_UNKNOWN, 237 MetricsProto.MetricsEvent.ACTION_ANOMALY_TRIGGERED, 238 SettingsEnums.PAGE_UNKNOWN, 239 SYSTEM_PACKAGE + "/" + VERSION_CODE, 240 ANOMALY_TYPE); 241 } 242 243 @Test saveAnomalyToDatabase_normalAppWithoutAutoRestriction_save()244 public void saveAnomalyToDatabase_normalAppWithoutAutoRestriction_save() { 245 final ArrayList<String> cookies = new ArrayList<>(); 246 cookies.add(SUBSCRIBER_COOKIES_NOT_AUTO_RESTRICTION); 247 mBundle.putStringArrayList(StatsManager.EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES, cookies); 248 doReturn(SYSTEM_PACKAGE).when(mBatteryUtils).getPackageName(anyInt()); 249 doReturn(false).when(mPowerAllowlistBackend).isSysAllowlisted(SYSTEM_PACKAGE); 250 doReturn(Process.FIRST_APPLICATION_UID).when( 251 mAnomalyDetectionJobService).extractUidFromStatsDimensionsValue(any()); 252 253 mAnomalyDetectionJobService.saveAnomalyToDatabase(mContext, 254 mUserManager, mBatteryDatabaseManager, mBatteryUtils, mPolicy, 255 mPowerAllowlistBackend, mContext.getContentResolver(), 256 mFeatureFactory.powerUsageFeatureProvider, mFeatureFactory.metricsFeatureProvider, 257 mBundle); 258 259 verify(mBatteryDatabaseManager).insertAnomaly(anyInt(), anyString(), eq(6), 260 eq(AnomalyDatabaseHelper.State.NEW), anyLong()); 261 verify(mFeatureFactory.metricsFeatureProvider).action(SettingsEnums.PAGE_UNKNOWN, 262 MetricsProto.MetricsEvent.ACTION_ANOMALY_TRIGGERED, 263 SettingsEnums.PAGE_UNKNOWN, 264 SYSTEM_PACKAGE + "/" + VERSION_CODE, 265 ANOMALY_TYPE); 266 } 267 268 @Test extractUidFromStatsDimensionsValue_extractCorrectUid()269 public void extractUidFromStatsDimensionsValue_extractCorrectUid() { 270 // Build an integer dimensions value. 271 final StatsDimensionsValue intValue = mock(StatsDimensionsValue.class); 272 when(intValue.isValueType(INT_VALUE_TYPE)).thenReturn(true); 273 when(intValue.getField()).thenReturn(AnomalyDetectionJobService.STATSD_UID_FILED); 274 when(intValue.getIntValue()).thenReturn(UID); 275 276 // Build a tuple dimensions value and put the previous integer dimensions value inside. 277 final StatsDimensionsValue tupleValue = mock(StatsDimensionsValue.class); 278 when(tupleValue.isValueType(TUPLE_VALUE_TYPE)).thenReturn(true); 279 final List<StatsDimensionsValue> statsDimensionsValues = new ArrayList<>(); 280 statsDimensionsValues.add(intValue); 281 when(tupleValue.getTupleValueList()).thenReturn(statsDimensionsValues); 282 283 assertThat(mAnomalyDetectionJobService.extractUidFromStatsDimensionsValue( 284 tupleValue)).isEqualTo(UID); 285 } 286 287 @Test extractUidFromStatsDimensionsValue_wrongFormat_returnNull()288 public void extractUidFromStatsDimensionsValue_wrongFormat_returnNull() { 289 // Build a float dimensions value 290 final StatsDimensionsValue floatValue = mock(StatsDimensionsValue.class); 291 when(floatValue.isValueType(FLOAT_VALUE_TYPE)).thenReturn(true); 292 when(floatValue.getField()).thenReturn(AnomalyDetectionJobService.STATSD_UID_FILED); 293 when(floatValue.getFloatValue()).thenReturn(0f); 294 295 assertThat(mAnomalyDetectionJobService.extractUidFromStatsDimensionsValue( 296 floatValue)).isEqualTo(AnomalyDetectionJobService.UID_NULL); 297 } 298 299 @Test stopJobWhileDequeuingWork_shouldNotCrash()300 public void stopJobWhileDequeuingWork_shouldNotCrash() { 301 when(mJobParameters.dequeueWork()).thenThrow(new SecurityException()); 302 303 mAnomalyDetectionJobService.onStopJob(mJobParameters); 304 305 // Should not crash even job is stopped 306 mAnomalyDetectionJobService.dequeueWork(mJobParameters); 307 } 308 309 @Test stopJobWhileCompletingWork_shouldNotCrash()310 public void stopJobWhileCompletingWork_shouldNotCrash() { 311 doThrow(new SecurityException()).when(mJobParameters).completeWork(any()); 312 313 mAnomalyDetectionJobService.onStopJob(mJobParameters); 314 315 // Should not crash even job is stopped 316 mAnomalyDetectionJobService.completeWork(mJobParameters, mJobWorkItem); 317 } 318 319 @Test restartWorkAfterBeenStopped_jobStarted()320 public void restartWorkAfterBeenStopped_jobStarted() { 321 mAnomalyDetectionJobService.onStopJob(mJobParameters); 322 mAnomalyDetectionJobService.onStartJob(mJobParameters); 323 324 assertThat(mAnomalyDetectionJobService.mIsJobCanceled).isFalse(); 325 } 326 } 327