From 9a80d6ebb8f166dae4a7dbef0f601efd9377a35c Mon Sep 17 00:00:00 2001 From: Lei Yu Date: Thu, 15 Mar 2018 15:55:07 -0700 Subject: [PATCH] Update the mechanism to upload new anomaly config 1. Move the config update to jobscheduler, so config is checked and updated every day. 2. Before the update, always delete the old config. 3. Move uploadIntent method to BatteryTipUtils. Bug: 74997752 Fixes: 74564143 Test: RunSettingsRoboTests Change-Id: If88759595d57a562b25542082f895fe8f207acdb --- AndroidManifest.xml | 3 + res/values/ids.xml | 1 + .../batterytip/AnomalyCleanupJobService.java | 3 +- .../batterytip/AnomalyConfigJobService.java | 116 ++++++++++++++++++ .../batterytip/AnomalyConfigReceiver.java | 42 +------ .../fuelgauge/batterytip/BatteryTipUtils.java | 17 +++ .../AnomalyConfigJobServiceTest.java | 112 +++++++++++++++++ 7 files changed, 252 insertions(+), 42 deletions(-) create mode 100644 src/com/android/settings/fuelgauge/batterytip/AnomalyConfigJobService.java create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/batterytip/AnomalyConfigJobServiceTest.java 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()); + } + +}