Add abnormal app page for battery settings

1. Add abnormal app page
2. Add strings for abnormal app page
3. Change AnomalyPreferenceController to open abnormal app page,
when there are more than one anomaly.
4. Add AnomalyPreference, who stores a reference of anomaly.

Also rename AnomalyPreferenceController to
AnomalySummaryPreferenceController because this controller is not
used to handle AnomalyPreference.

Following cls will add summary and icon for each abnormal app.

Bug: 37681665
Test: RunSettingsRoboTests
Change-Id: I4266f906476ff8daccd962572c8cfa99f948080a
This commit is contained in:
jackqdyulei
2017-04-26 14:25:51 -07:00
parent 4aa3358c4c
commit 55a97a9ad4
11 changed files with 426 additions and 44 deletions

View File

@@ -4540,6 +4540,9 @@
<!-- Subtitle for list of packages -->
<string name="packages_subtitle">Included packages</string>
<!-- Activity title for battery abnormal details page [CHAR LIMIT=60] -->
<string name="battery_abnormal_details_title">Abnormal app behavior</string>
<!-- Label for power consumed by the screen -->
<string name="power_screen">Screen</string>
<!-- Label for power consumed by the flashlight -->

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 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.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:title="@string/battery_abnormal_details_title">
<PreferenceCategory
android:key="app_abnormal_list"/>
</PreferenceScreen>

View File

@@ -0,0 +1,132 @@
/*
* Copyright (C) 2017 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;
import android.content.Context;
import android.os.Bundle;
import android.os.UserHandle;
import android.support.v14.preference.PreferenceFragment;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceGroup;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.core.PreferenceController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.fuelgauge.anomaly.Anomaly;
import com.android.settings.fuelgauge.anomaly.AnomalyDialogFragment;
import com.android.settings.fuelgauge.anomaly.AnomalyPreference;
import java.util.List;
/**
* Fragment to show a list of anomaly apps, where user could handle these anomalies
*/
public class PowerUsageAnomalyDetails extends DashboardFragment implements
AnomalyDialogFragment.AnomalyDialogListener {
public static final String TAG = "PowerAbnormalUsageDetail";
@VisibleForTesting
static final String EXTRA_ANOMALY_LIST = "anomaly_list";
private static final int REQUEST_ANOMALY_ACTION = 0;
private static final String KEY_PREF_ANOMALY_LIST = "app_abnormal_list";
@VisibleForTesting
List<Anomaly> mAnomalies;
@VisibleForTesting
PreferenceGroup mAbnormalListGroup;
public static void startBatteryAbnormalPage(SettingsActivity caller,
PreferenceFragment fragment, List<Anomaly> anomalies) {
Bundle args = new Bundle();
args.putParcelableList(EXTRA_ANOMALY_LIST, anomalies);
caller.startPreferencePanelAsUser(fragment, PowerUsageAnomalyDetails.class.getName(), args,
R.string.battery_abnormal_details_title, null,
new UserHandle(UserHandle.myUserId()));
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
mAnomalies = getArguments().getParcelableArrayList(EXTRA_ANOMALY_LIST);
mAbnormalListGroup = (PreferenceGroup) findPreference(KEY_PREF_ANOMALY_LIST);
}
@Override
public void onResume() {
super.onResume();
refreshUi();
}
@Override
public boolean onPreferenceTreeClick(Preference preference) {
if (preference instanceof AnomalyPreference) {
AnomalyPreference anomalyPreference = (AnomalyPreference) preference;
final Anomaly anomaly = anomalyPreference.getAnomaly();
AnomalyDialogFragment dialogFragment = AnomalyDialogFragment.newInstance(anomaly);
dialogFragment.setTargetFragment(this, REQUEST_ANOMALY_ACTION);
dialogFragment.show(getFragmentManager(), TAG);
return true;
}
return super.onPreferenceTreeClick(preference);
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.power_abnormal_detail;
}
@Override
protected List<PreferenceController> getPreferenceControllers(Context context) {
return null;
}
@Override
public int getMetricsCategory() {
//TODO(b/37681923): add correct metrics category
return 0;
}
void refreshUi() {
//TODO(b/37681665): cache the preference so we don't need to create new one every time.
mAbnormalListGroup.removeAll();
for (int i = 0, size = mAnomalies.size(); i < size; i++) {
final Anomaly anomaly = mAnomalies.get(i);
Preference pref = new AnomalyPreference(getPrefContext(), anomaly);
mAbnormalListGroup.addPreference(pref);
}
}
@Override
public void onAnomalyHandled(Anomaly anomaly) {
mAnomalies.remove(anomaly);
refreshUi();
}
}

View File

@@ -64,7 +64,7 @@ import com.android.settings.display.TimeoutPreferenceController;
import com.android.settings.fuelgauge.anomaly.Anomaly;
import com.android.settings.fuelgauge.anomaly.AnomalyDialogFragment;
import com.android.settings.fuelgauge.anomaly.AnomalyLoader;
import com.android.settings.fuelgauge.anomaly.AnomalyPreferenceController;
import com.android.settings.fuelgauge.anomaly.AnomalySummaryPreferenceController;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.widget.FooterPreferenceMixin;
@@ -125,7 +125,7 @@ public class PowerUsageSummary extends PowerUsageBase implements
private LayoutPreference mBatteryLayoutPref;
private PreferenceGroup mAppListGroup;
private AnomalyPreferenceController mAnomalyPreferenceController;
private AnomalySummaryPreferenceController mAnomalySummaryPreferenceController;
private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;
private LoaderManager.LoaderCallbacks<List<Anomaly>> mAnomalyLoaderCallbacks =
@@ -139,7 +139,7 @@ public class PowerUsageSummary extends PowerUsageBase implements
@Override
public void onLoadFinished(Loader<List<Anomaly>> loader, List<Anomaly> data) {
// show high usage preference if possible
mAnomalyPreferenceController.updateAnomalyPreference(data);
mAnomalySummaryPreferenceController.updateHighUsagePreference(data);
}
@Override
@@ -159,8 +159,8 @@ public class PowerUsageSummary extends PowerUsageBase implements
mLastFullChargePref = (PowerGaugePreference) findPreference(
KEY_TIME_SINCE_LAST_FULL_CHARGE);
mFooterPreferenceMixin.createFooterPreference().setTitle(R.string.battery_footer_summary);
mAnomalyPreferenceController = new AnomalyPreferenceController(this);
mAnomalySummaryPreferenceController = new AnomalySummaryPreferenceController(
(SettingsActivity) getActivity(), this);
mBatteryUtils = BatteryUtils.getInstance(getContext());
initFeatureProvider();
@@ -193,7 +193,7 @@ public class PowerUsageSummary extends PowerUsageBase implements
@Override
public boolean onPreferenceTreeClick(Preference preference) {
if (mAnomalyPreferenceController.onPreferenceTreeClick(preference)) {
if (mAnomalySummaryPreferenceController.onPreferenceTreeClick(preference)) {
return true;
}
if (KEY_BATTERY_HEADER.equals(preference.getKey())) {
@@ -727,7 +727,7 @@ public class PowerUsageSummary extends PowerUsageBase implements
@Override
public void onAnomalyHandled(Anomaly anomaly) {
mAnomalyPreferenceController.hideAnomalyPreference();
mAnomalySummaryPreferenceController.hideHighUsagePreference();
}
private static class SummaryProvider implements SummaryLoader.SummaryProvider {

View File

@@ -51,7 +51,6 @@ public class Anomaly implements Parcelable {
public final int type;
public final int uid;
public final long wakelockTimeMs;
/**
* Display name of this anomaly, usually it is the app name
*/
@@ -98,7 +97,6 @@ public class Anomaly implements Parcelable {
}
Anomaly other = (Anomaly) obj;
return type == other.type
&& uid == other.uid
&& wakelockTimeMs == other.wakelockTimeMs

View File

@@ -0,0 +1,27 @@
package com.android.settings.fuelgauge.anomaly;
import android.content.Context;
import android.support.v7.preference.Preference;
import com.android.internal.annotations.VisibleForTesting;
/**
* Preference that stores {@link Anomaly}
*/
public class AnomalyPreference extends Preference {
private Anomaly mAnomaly;
public AnomalyPreference(Context context, Anomaly anomaly) {
super(context);
mAnomaly = anomaly;
if (anomaly != null) {
setTitle(anomaly.displayName);
}
}
public Anomaly getAnomaly() {
return mAnomaly;
}
}

View File

@@ -20,15 +20,16 @@ import android.support.annotation.VisibleForTesting;
import android.support.v14.preference.PreferenceFragment;
import android.support.v7.preference.Preference;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.fuelgauge.PowerUsageAnomalyDetails;
import java.util.List;
/**
* Manager that responsible for updating anomaly preference and handling preference click.
* Manager that responsible for updating high usage preference and handling preference click.
*/
public class AnomalyPreferenceController {
private static final String TAG = "AnomalyPreferenceController";
public class AnomalySummaryPreferenceController {
private static final String TAG = "HighUsagePreferenceController";
@VisibleForTesting
static final String ANOMALY_KEY = "high_usage";
private static final int REQUEST_ANOMALY_ACTION = 0;
@@ -37,11 +38,14 @@ public class AnomalyPreferenceController {
Preference mAnomalyPreference;
@VisibleForTesting
List<Anomaly> mAnomalies;
private SettingsActivity mSettingsActivity;
public AnomalyPreferenceController(PreferenceFragment fragment) {
public AnomalySummaryPreferenceController(SettingsActivity activity,
PreferenceFragment fragment) {
mFragment = fragment;
mSettingsActivity = activity;
mAnomalyPreference = mFragment.getPreferenceScreen().findPreference(ANOMALY_KEY);
hideAnomalyPreference();
hideHighUsagePreference();
}
public boolean onPreferenceTreeClick(Preference preference) {
@@ -52,7 +56,8 @@ public class AnomalyPreferenceController {
dialogFragment.setTargetFragment(mFragment, REQUEST_ANOMALY_ACTION);
dialogFragment.show(mFragment.getFragmentManager(), TAG);
} else {
//TODO(b/37681665): start a new fragment to handle it
PowerUsageAnomalyDetails.startBatteryAbnormalPage(mSettingsActivity, mFragment,
mAnomalies);
}
return true;
}
@@ -65,7 +70,7 @@ public class AnomalyPreferenceController {
*
* @param anomalies used to update the summary, this method will store a reference of it
*/
public void updateAnomalyPreference(List<Anomaly> anomalies) {
public void updateHighUsagePreference(List<Anomaly> anomalies) {
mAnomalies = anomalies;
if (!mAnomalies.isEmpty()) {
@@ -74,7 +79,7 @@ public class AnomalyPreferenceController {
}
}
public void hideAnomalyPreference() {
public void hideHighUsagePreference() {
mAnomalyPreference.setVisible(false);
}
}

View File

@@ -1,6 +1,7 @@
com.android.settings.language.LanguageAndRegionSettings
com.android.settings.notification.ZenModePrioritySettings
com.android.settings.accounts.AccountDetailDashboardFragment
com.android.settings.fuelgauge.PowerUsageAnomalyDetails
com.android.settings.fuelgauge.PowerUsageDetail
com.android.settings.fuelgauge.AdvancedPowerUsageDetail
com.android.settings.deviceinfo.StorageProfileFragment

View File

@@ -0,0 +1,139 @@
/*
* Copyright (C) 2017 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;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceCategory;
import android.support.v7.preference.PreferenceGroup;
import android.support.v7.preference.PreferenceManager;
import com.android.settings.SettingsActivity;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.fuelgauge.anomaly.Anomaly;
import com.android.settings.fuelgauge.anomaly.AnomalyPreference;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
import java.util.List;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class PowerUsageAnomalyDetailsTest {
private static final String NAME_APP_1 = "app1";
private static final String NAME_APP_2 = "app2";
@Mock
private SettingsActivity mSettingsActivity;
@Mock
private PreferenceManager mPreferenceManager;
private Context mContext;
private PowerUsageAnomalyDetails mFragment;
private PreferenceGroup mAbnormalListGroup;
private Bundle mBundle;
private List<Anomaly> mAnomalyList;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mAbnormalListGroup = spy(new PreferenceCategory(mContext));
mAnomalyList = new ArrayList<>();
Anomaly anomaly1 = new Anomaly.Builder()
.setType(Anomaly.AnomalyType.WAKE_LOCK)
.setDisplayName(NAME_APP_1)
.build();
mAnomalyList.add(anomaly1);
Anomaly anomaly2 = new Anomaly.Builder()
.setType(Anomaly.AnomalyType.WAKE_LOCK)
.setDisplayName(NAME_APP_2)
.build();
mAnomalyList.add(anomaly2);
mFragment = spy(new PowerUsageAnomalyDetails());
mFragment.mAbnormalListGroup = mAbnormalListGroup;
mFragment.mAnomalies = mAnomalyList;
doReturn(mPreferenceManager).when(mFragment).getPreferenceManager();
doReturn(mContext).when(mPreferenceManager).getContext();
}
@Test
public void testRefreshUi_dataCorrect() {
final List<Anomaly> testAnomalyList = new ArrayList<>();
final ArgumentCaptor<Preference> preferenceCaptor = ArgumentCaptor.forClass(
Preference.class);
Answer<Void> prefCallable = new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
testAnomalyList.add(
((AnomalyPreference) preferenceCaptor.getValue()).getAnomaly());
return null;
}
};
doAnswer(prefCallable).when(mAbnormalListGroup).addPreference(preferenceCaptor.capture());
mFragment.refreshUi();
assertThat(testAnomalyList).containsExactlyElementsIn(mAnomalyList);
}
@Test
public void testStartBatteryAbnormalPage_dataCorrect() {
final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
Answer<Void> bundleCallable = new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Exception {
mBundle = bundleCaptor.getValue();
return null;
}
};
doAnswer(bundleCallable).when(mSettingsActivity).startPreferencePanelAsUser(any(),
anyString(),
bundleCaptor.capture(), anyInt(), any(), any());
PowerUsageAnomalyDetails.startBatteryAbnormalPage(mSettingsActivity, mFragment,
mAnomalyList);
assertThat(mBundle.getParcelableArrayList(
PowerUsageAnomalyDetails.EXTRA_ANOMALY_LIST)).isEqualTo(mAnomalyList);
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2017 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.anomaly;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class AnomalyPreferenceTest {
@Anomaly.AnomalyType
private static final int ANOMALY_TYPE = Anomaly.AnomalyType.WAKE_LOCK;
private static final String PACKAGE_NAME = "com.android.app";
private static final String DISPLAY_NAME = "app";
private static final int UID = 111;
private Context mContext;
private Anomaly mAnomaly;
private AnomalyPreference mAnomalyPreference;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mAnomaly = new Anomaly.Builder()
.setType(ANOMALY_TYPE)
.setPackageName(PACKAGE_NAME)
.setDisplayName(DISPLAY_NAME)
.setUid(UID)
.build();
}
@Test
public void testAnomalyPreference_containsCorrectData() {
mAnomalyPreference = new AnomalyPreference(mContext, mAnomaly);
assertThat(mAnomalyPreference.getTitle()).isEqualTo(DISPLAY_NAME);
}
}

View File

@@ -25,28 +25,15 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.v14.preference.PreferenceFragment;
import android.support.v7.preference.Preference;
import com.android.settings.R;
import com.android.settings.Settings;
import com.android.settings.SettingsActivity;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.core.PreferenceController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.fuelgauge.ButtonActionDialogFragmentTest;
import com.android.settings.testutils.shadow.SettingsShadowResources;
import com.android.settings.testutils.shadow.ShadowDynamicIndexableContentMonitor;
import com.android.settings.testutils.shadow.ShadowEventLogWriter;
import org.junit.Before;
import org.junit.Test;
@@ -56,16 +43,13 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowAlertDialog;
import org.robolectric.shadows.ShadowDialog;
import org.robolectric.util.FragmentTestUtil;
import java.util.ArrayList;
import java.util.List;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class AnomalyPreferenceControllerTest {
public class AnomalySummaryPreferenceControllerTest {
@Anomaly.AnomalyType
private static final int ANOMALY_TYPE = Anomaly.AnomalyType.WAKE_LOCK;
private static final String PACKAGE_NAME = "com.android.app";
@@ -77,7 +61,9 @@ public class AnomalyPreferenceControllerTest {
private FragmentManager mFragmentManager;
@Mock
private FragmentTransaction mFragmentTransaction;
private AnomalyPreferenceController mAnomalyPreferenceController;
@Mock
private SettingsActivity mSettingsActivity;
private AnomalySummaryPreferenceController mAnomalySummaryPreferenceController;
private Preference mPreference;
private Context mContext;
private List<Anomaly> mAnomalyList;
@@ -88,7 +74,7 @@ public class AnomalyPreferenceControllerTest {
mContext = RuntimeEnvironment.application;
mPreference = new Preference(mContext);
mPreference.setKey(AnomalyPreferenceController.ANOMALY_KEY);
mPreference.setKey(AnomalySummaryPreferenceController.ANOMALY_KEY);
when(mFragment.getPreferenceManager().findPreference(any())).thenReturn(mPreference);
when(mFragment.getFragmentManager()).thenReturn(mFragmentManager);
when(mFragmentManager.beginTransaction()).thenReturn(mFragmentTransaction);
@@ -101,22 +87,23 @@ public class AnomalyPreferenceControllerTest {
.build();
mAnomalyList.add(anomaly);
mAnomalyPreferenceController = new AnomalyPreferenceController(mFragment);
mAnomalySummaryPreferenceController = new AnomalySummaryPreferenceController(
mSettingsActivity, mFragment);
}
@Test
public void testUpdateAnomalyPreference_hasCorrectData() {
mAnomalyPreferenceController.updateAnomalyPreference(mAnomalyList);
public void testUpdateHighUsagePreference_hasCorrectData() {
mAnomalySummaryPreferenceController.updateHighUsagePreference(mAnomalyList);
//add more test when this method is complete
assertThat(mAnomalyPreferenceController.mAnomalies).isEqualTo(mAnomalyList);
assertThat(mAnomalySummaryPreferenceController.mAnomalies).isEqualTo(mAnomalyList);
}
@Test
public void testOnPreferenceTreeClick_oneAnomaly_showDialog() {
mAnomalyPreferenceController.mAnomalies = mAnomalyList;
mAnomalySummaryPreferenceController.mAnomalies = mAnomalyList;
mAnomalyPreferenceController.onPreferenceTreeClick(mPreference);
mAnomalySummaryPreferenceController.onPreferenceTreeClick(mPreference);
verify(mFragmentManager).beginTransaction();
verify(mFragmentTransaction).add(any(), anyString());