diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index a94938edd63..3b74473265e 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -3258,6 +3258,9 @@
+
+
diff --git a/res/values/ids.xml b/res/values/ids.xml
index 76322ffaed6..57031e1e27b 100644
--- a/res/values/ids.xml
+++ b/res/values/ids.xml
@@ -19,6 +19,7 @@
+
diff --git a/src/com/android/settings/fuelgauge/batterytip/AnomalyCleanupJobService.java b/src/com/android/settings/fuelgauge/batterytip/AnomalyCleanupJobService.java
index 17aba2b44c8..46744f7e7a0 100644
--- a/src/com/android/settings/fuelgauge/batterytip/AnomalyCleanupJobService.java
+++ b/src/com/android/settings/fuelgauge/batterytip/AnomalyCleanupJobService.java
@@ -45,8 +45,7 @@ public class AnomalyCleanupJobService extends JobService {
new JobInfo.Builder(R.id.job_anomaly_clean_up, component)
.setPeriodic(CLEAN_UP_FREQUENCY_MS)
.setRequiresDeviceIdle(true)
- .setRequiresCharging(true)
- .setPersisted(true);
+ .setRequiresCharging(true);
if (jobScheduler.schedule(jobBuilder.build()) != JobScheduler.RESULT_SUCCESS) {
Log.i(TAG, "Anomaly clean up job service schedule failed.");
diff --git a/src/com/android/settings/fuelgauge/batterytip/AnomalyConfigJobService.java b/src/com/android/settings/fuelgauge/batterytip/AnomalyConfigJobService.java
new file mode 100644
index 00000000000..1a650880eac
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batterytip/AnomalyConfigJobService.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.batterytip;
+
+import android.app.StatsManager;
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.provider.Settings;
+import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+
+import com.android.settings.R;
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.util.concurrent.TimeUnit;
+
+/** A JobService check whether to update the anomaly config periodically */
+public class AnomalyConfigJobService extends JobService {
+ private static final String TAG = "AnomalyConfigJobService";
+
+ @VisibleForTesting
+ static final String PREF_DB = "anomaly_pref";
+ private static final String KEY_ANOMALY_CONFIG_VERSION = "anomaly_config_version";
+ private static final int DEFAULT_VERSION = 0;
+
+ @VisibleForTesting
+ static final long CONFIG_UPDATE_FREQUENCY_MS = TimeUnit.DAYS.toMillis(1);
+
+ public static void scheduleConfigUpdate(Context context) {
+ final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+
+ final ComponentName component = new ComponentName(context, AnomalyConfigJobService.class);
+ final JobInfo.Builder jobBuilder =
+ new JobInfo.Builder(R.id.job_anomaly_config_update, component)
+ .setPeriodic(CONFIG_UPDATE_FREQUENCY_MS)
+ .setRequiresDeviceIdle(true)
+ .setRequiresCharging(true);
+
+ if (jobScheduler.schedule(jobBuilder.build()) != JobScheduler.RESULT_SUCCESS) {
+ Log.i(TAG, "Anomaly config update job service schedule failed.");
+ }
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ ThreadUtils.postOnBackgroundThread(() -> {
+ final StatsManager statsManager = getSystemService(StatsManager.class);
+ checkAnomalyConfig(statsManager);
+ BatteryTipUtils.uploadAnomalyPendingIntent(this, statsManager);
+ jobFinished(params, false /* wantsReschedule */);
+ });
+
+ return true;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters jobParameters) {
+ return false;
+ }
+
+ @VisibleForTesting
+ synchronized void checkAnomalyConfig(StatsManager statsManager) {
+ final SharedPreferences sharedPreferences = getSharedPreferences(PREF_DB,
+ Context.MODE_PRIVATE);
+ final int currentVersion = sharedPreferences.getInt(KEY_ANOMALY_CONFIG_VERSION,
+ DEFAULT_VERSION);
+ final int newVersion = Settings.Global.getInt(getContentResolver(),
+ Settings.Global.ANOMALY_CONFIG_VERSION, DEFAULT_VERSION);
+ final String rawConfig = Settings.Global.getString(getContentResolver(),
+ Settings.Global.ANOMALY_CONFIG);
+ Log.i(TAG, "CurrentVersion: " + currentVersion + " new version: " + newVersion);
+
+ if (newVersion > currentVersion) {
+ statsManager.removeConfiguration(StatsManagerConfig.ANOMALY_CONFIG_KEY);
+ if (!TextUtils.isEmpty(rawConfig)) {
+ try {
+ final byte[] config = Base64.decode(rawConfig, Base64.DEFAULT);
+ if (statsManager.addConfiguration(StatsManagerConfig.ANOMALY_CONFIG_KEY,
+ config)) {
+ Log.i(TAG, "Upload the anomaly config. configKey: "
+ + StatsManagerConfig.ANOMALY_CONFIG_KEY);
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ editor.putInt(KEY_ANOMALY_CONFIG_VERSION, newVersion);
+ editor.commit();
+ } else {
+ Log.i(TAG, "Upload the anomaly config failed. configKey: "
+ + StatsManagerConfig.ANOMALY_CONFIG_KEY);
+ }
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Anomaly raw config is in wrong format", e);
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/batterytip/AnomalyConfigReceiver.java b/src/com/android/settings/fuelgauge/batterytip/AnomalyConfigReceiver.java
index d81dc34efcd..dcacaae3f06 100644
--- a/src/com/android/settings/fuelgauge/batterytip/AnomalyConfigReceiver.java
+++ b/src/com/android/settings/fuelgauge/batterytip/AnomalyConfigReceiver.java
@@ -21,9 +21,6 @@ import android.app.StatsManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.content.SharedPreferences;
-import android.provider.Settings;
-import android.util.Base64;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -34,10 +31,6 @@ import com.android.internal.annotations.VisibleForTesting;
*/
public class AnomalyConfigReceiver extends BroadcastReceiver {
private static final String TAG = "AnomalyConfigReceiver";
- private static final int REQUEST_CODE = 0;
- private static final String PREF_DB = "anomaly_pref";
- private static final String KEY_ANOMALY_CONFIG_VERSION = "anomaly_config_version";
- private static final int DEFAULT_VERSION = 0;
@Override
public void onReceive(Context context, Intent intent) {
@@ -46,14 +39,9 @@ public class AnomalyConfigReceiver extends BroadcastReceiver {
final StatsManager statsManager = context.getSystemService(StatsManager.class);
// Check whether to update the config
- checkAnomalyConfig(context, statsManager);
+ AnomalyConfigJobService.scheduleConfigUpdate(context);
- // Upload PendingIntent to StatsManager
- final Intent extraIntent = new Intent(context, AnomalyDetectionReceiver.class);
- final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, REQUEST_CODE,
- extraIntent, PendingIntent.FLAG_UPDATE_CURRENT);
-
- uploadPendingIntent(statsManager, pendingIntent);
+ BatteryTipUtils.uploadAnomalyPendingIntent(context, statsManager);
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
AnomalyCleanupJobService.scheduleCleanUp(context);
@@ -69,30 +57,4 @@ public class AnomalyConfigReceiver extends BroadcastReceiver {
statsManager.setBroadcastSubscriber(StatsManagerConfig.ANOMALY_CONFIG_KEY,
StatsManagerConfig.SUBSCRIBER_ID, pendingIntent);
}
-
- private void checkAnomalyConfig(Context context, StatsManager statsManager) {
- final SharedPreferences sharedPreferences = context.getSharedPreferences(PREF_DB,
- Context.MODE_PRIVATE);
- final int currentVersion = sharedPreferences.getInt(KEY_ANOMALY_CONFIG_VERSION,
- DEFAULT_VERSION);
- final int newVersion = Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.ANOMALY_CONFIG_VERSION, DEFAULT_VERSION);
- Log.i(TAG, "CurrentVersion: " + currentVersion + " new version: " + newVersion);
-
- if (newVersion > currentVersion) {
- final byte[] config = Base64.decode(
- Settings.Global.getString(context.getContentResolver(),
- Settings.Global.ANOMALY_CONFIG), Base64.DEFAULT);
- if (statsManager.addConfiguration(StatsManagerConfig.ANOMALY_CONFIG_KEY, config)) {
- Log.i(TAG, "Upload the anomaly config. configKey: "
- + StatsManagerConfig.ANOMALY_CONFIG_KEY);
- SharedPreferences.Editor editor = sharedPreferences.edit();
- editor.putInt(KEY_ANOMALY_CONFIG_VERSION, newVersion);
- editor.apply();
- } else {
- Log.i(TAG, "Upload the anomaly config failed. configKey: "
- + StatsManagerConfig.ANOMALY_CONFIG_KEY);
- }
- }
- }
}
diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtils.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtils.java
index 5ff5430aedb..e9e30dec5cc 100644
--- a/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtils.java
+++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtils.java
@@ -16,6 +16,11 @@
package com.android.settings.fuelgauge.batterytip;
+import android.app.PendingIntent;
+import android.app.StatsManager;
+import android.content.Context;
+import android.content.Intent;
+
import com.android.settings.SettingsActivity;
import com.android.settings.core.InstrumentedPreferenceFragment;
import com.android.settings.fuelgauge.batterytip.actions.BatterySaverAction;
@@ -32,6 +37,7 @@ import com.android.settings.fuelgauge.batterytip.tips.UnrestrictAppTip;
* Utility class for {@link BatteryTip}
*/
public class BatteryTipUtils {
+ private static final int REQUEST_CODE = 0;
/**
* Get a corresponding action based on {@code batteryTip}
@@ -60,4 +66,15 @@ public class BatteryTipUtils {
return null;
}
}
+
+ /**
+ * Upload the {@link PendingIntent} to {@link StatsManager} for anomaly detection
+ */
+ public static void uploadAnomalyPendingIntent(Context context, StatsManager statsManager) {
+ final Intent extraIntent = new Intent(context, AnomalyDetectionReceiver.class);
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, REQUEST_CODE,
+ extraIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ statsManager.setBroadcastSubscriber(StatsManagerConfig.ANOMALY_CONFIG_KEY,
+ StatsManagerConfig.SUBSCRIBER_ID, pendingIntent);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AnomalyConfigJobServiceTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AnomalyConfigJobServiceTest.java
new file mode 100644
index 00000000000..e1b85aaddb1
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AnomalyConfigJobServiceTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.batterytip;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.robolectric.RuntimeEnvironment.application;
+
+import android.app.StatsManager;
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.provider.Settings;
+
+import com.android.settings.R;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.Shadows;
+import org.robolectric.shadows.ShadowJobScheduler;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+public class AnomalyConfigJobServiceTest {
+
+ private static final int ANOMALY_CONFIG_VERSION = 1;
+ private static final String ANOMALY_CONFIG = "X64s";
+ @Mock
+ private StatsManager mStatsManager;
+
+ private AnomalyConfigJobService mJobService;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mJobService = spy(new AnomalyConfigJobService());
+ doReturn(application.getSharedPreferences(AnomalyConfigJobService.PREF_DB,
+ Context.MODE_PRIVATE)).when(mJobService).getSharedPreferences(anyString(),
+ anyInt());
+ doReturn(application.getContentResolver()).when(mJobService).getContentResolver();
+ }
+
+ @Test
+ public void testScheduleCleanUp() {
+ AnomalyConfigJobService.scheduleConfigUpdate(application);
+
+ ShadowJobScheduler shadowJobScheduler =
+ Shadows.shadowOf(application.getSystemService(JobScheduler.class));
+ List pendingJobs = shadowJobScheduler.getAllPendingJobs();
+ assertEquals(1, pendingJobs.size());
+ JobInfo pendingJob = pendingJobs.get(0);
+ assertThat(pendingJob.getId()).isEqualTo(R.id.job_anomaly_config_update);
+ assertThat(pendingJob.getIntervalMillis()).isEqualTo(TimeUnit.DAYS.toMillis(1));
+ assertThat(pendingJob.isRequireDeviceIdle()).isTrue();
+ assertThat(pendingJob.isRequireCharging()).isTrue();
+ }
+
+ @Test
+ public void checkAnomalyConfig_newConfigExist_removeOldConfig() {
+ Settings.Global.putInt(application.getContentResolver(),
+ Settings.Global.ANOMALY_CONFIG_VERSION, ANOMALY_CONFIG_VERSION);
+ Settings.Global.putString(application.getContentResolver(), Settings.Global.ANOMALY_CONFIG,
+ ANOMALY_CONFIG);
+
+ mJobService.checkAnomalyConfig(mStatsManager);
+
+ verify(mStatsManager).removeConfiguration(StatsManagerConfig.ANOMALY_CONFIG_KEY);
+ }
+
+ @Test
+ public void checkAnomalyConfig_newConfigExist_uploadNewConfig() {
+ Settings.Global.putInt(application.getContentResolver(),
+ Settings.Global.ANOMALY_CONFIG_VERSION, ANOMALY_CONFIG_VERSION);
+ Settings.Global.putString(application.getContentResolver(), Settings.Global.ANOMALY_CONFIG,
+ ANOMALY_CONFIG);
+
+ mJobService.checkAnomalyConfig(mStatsManager);
+
+ verify(mStatsManager).addConfiguration(eq(StatsManagerConfig.ANOMALY_CONFIG_KEY), any());
+ }
+
+}