• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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