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()); + } + +}