diff --git a/res/xml/power_usage_summary.xml b/res/xml/power_usage_summary.xml
index abd659e6076..3c685c04d50 100644
--- a/res/xml/power_usage_summary.xml
+++ b/res/xml/power_usage_summary.xml
@@ -25,6 +25,9 @@
android:selectable="true"
android:layout="@layout/battery_header"/>
+
+
diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java
index bcf830bc442..349a08eb7be 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java
@@ -17,9 +17,11 @@
package com.android.settings.fuelgauge;
import android.app.Activity;
+import android.app.LoaderManager;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.Loader;
import android.graphics.drawable.Drawable;
import android.os.BatteryStats;
import android.os.Build;
@@ -59,6 +61,10 @@ import com.android.settings.dashboard.SummaryLoader;
import com.android.settings.display.AutoBrightnessPreferenceController;
import com.android.settings.display.BatteryPercentagePreferenceController;
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.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.widget.FooterPreferenceMixin;
@@ -74,7 +80,8 @@ import java.util.List;
* Displays a list of apps and subsystems that consume power, ordered by how much power was
* consumed since the last time it was unplugged.
*/
-public class PowerUsageSummary extends PowerUsageBase {
+public class PowerUsageSummary extends PowerUsageBase implements
+ AnomalyDialogFragment.AnomalyDialogListener {
static final String TAG = "PowerUsageSummary";
@@ -84,6 +91,7 @@ public class PowerUsageSummary extends PowerUsageBase {
private static final String KEY_BATTERY_HEADER = "battery_header";
private static final int MAX_ITEMS_TO_LIST = USE_FAKE_DATA ? 30 : 10;
private static final int MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP = 10;
+ private static final int ANOMALY_LOADER = 1;
private static final String KEY_SCREEN_USAGE = "screen_usage";
private static final String KEY_TIME_SINCE_LAST_FULL_CHARGE = "last_full_charge";
@@ -117,8 +125,29 @@ public class PowerUsageSummary extends PowerUsageBase {
private LayoutPreference mBatteryLayoutPref;
private PreferenceGroup mAppListGroup;
+ private AnomalyPreferenceController mAnomalyPreferenceController;
private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;
+ private LoaderManager.LoaderCallbacks> mAnomalyLoaderCallbacks =
+ new LoaderManager.LoaderCallbacks>() {
+
+ @Override
+ public Loader> onCreateLoader(int id, Bundle args) {
+ return new AnomalyLoader(getContext(), mStatsHelper);
+ }
+
+ @Override
+ public void onLoadFinished(Loader> loader, List data) {
+ // show high usage preference if possible
+ mAnomalyPreferenceController.updateAnomalyPreference(data);
+ }
+
+ @Override
+ public void onLoaderReset(Loader> loader) {
+
+ }
+ };
+
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
@@ -130,6 +159,7 @@ public class PowerUsageSummary extends PowerUsageBase {
mLastFullChargePref = (PowerGaugePreference) findPreference(
KEY_TIME_SINCE_LAST_FULL_CHARGE);
mFooterPreferenceMixin.createFooterPreference().setTitle(R.string.battery_footer_summary);
+ mAnomalyPreferenceController = new AnomalyPreferenceController(this);
mBatteryUtils = BatteryUtils.getInstance(getContext());
@@ -163,6 +193,9 @@ public class PowerUsageSummary extends PowerUsageBase {
@Override
public boolean onPreferenceTreeClick(Preference preference) {
+ if (mAnomalyPreferenceController.onPreferenceTreeClick(preference)) {
+ return true;
+ }
if (KEY_BATTERY_HEADER.equals(preference.getKey())) {
performBatteryHeaderClick();
return true;
@@ -403,6 +436,8 @@ public class PowerUsageSummary extends PowerUsageBase {
return;
}
+ getLoaderManager().initLoader(ANOMALY_LOADER, null, mAnomalyLoaderCallbacks);
+
cacheRemoveAllPrefs(mAppListGroup);
mAppListGroup.setOrderingAsAdded(false);
boolean addedSome = false;
@@ -690,6 +725,11 @@ public class PowerUsageSummary extends PowerUsageBase {
}
};
+ @Override
+ public void onAnomalyHandled(Anomaly anomaly) {
+ mAnomalyPreferenceController.hideAnomalyPreference();
+ }
+
private static class SummaryProvider implements SummaryLoader.SummaryProvider {
private final Context mContext;
private final SummaryLoader mLoader;
diff --git a/src/com/android/settings/fuelgauge/anomaly/Anomaly.java b/src/com/android/settings/fuelgauge/anomaly/Anomaly.java
new file mode 100644
index 00000000000..a10d3f4047e
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/Anomaly.java
@@ -0,0 +1,136 @@
+/*
+ * 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 android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Data that represents an app has been detected as anomaly. It contains
+ *
+ * 1. Basic information of the app(i.e. uid, package name)
+ * 2. Type of anomaly
+ * 3. Data that has been detected as anomaly(i.e wakelock time)
+ */
+public class Anomaly implements Parcelable {
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({AnomalyType.WAKE_LOCK})
+ public @interface AnomalyType {
+ int WAKE_LOCK = 0;
+ }
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({AnomalyActionType.FORCE_STOP})
+ public @interface AnomalyActionType {
+ int FORCE_STOP = 0;
+ }
+
+ /**
+ * Type of this this anomaly
+ */
+ public final int type;
+ public final int uid;
+ public final long wakelockTimeMs;
+
+ /**
+ * Display name of this anomaly, usually it is the app name
+ */
+ public final String displayName;
+ public final String packageName;
+
+ private Anomaly(Builder builder) {
+ type = builder.mType;
+ uid = builder.mUid;
+ displayName = builder.mDisplayName;
+ packageName = builder.mPackageName;
+ wakelockTimeMs = builder.mWakeLockTimeMs;
+ }
+
+ private Anomaly(Parcel in) {
+ type = in.readInt();
+ uid = in.readInt();
+ displayName = in.readString();
+ packageName = in.readString();
+ wakelockTimeMs = in.readLong();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(type);
+ dest.writeInt(uid);
+ dest.writeString(displayName);
+ dest.writeString(packageName);
+ dest.writeLong(wakelockTimeMs);
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+ public Anomaly createFromParcel(Parcel in) {
+ return new Anomaly(in);
+ }
+
+ public Anomaly[] newArray(int size) {
+ return new Anomaly[size];
+ }
+ };
+
+ public static final class Builder {
+ @AnomalyType
+ private int mType;
+ private int mUid;
+ private String mDisplayName;
+ private String mPackageName;
+ private long mWakeLockTimeMs;
+
+ public Builder setType(@AnomalyType int type) {
+ mType = type;
+ return this;
+ }
+
+ public Builder setUid(int uid) {
+ mUid = uid;
+ return this;
+ }
+
+ public Builder setDisplayName(String displayName) {
+ mDisplayName = displayName;
+ return this;
+ }
+
+ public Builder setPackageName(String packageName) {
+ mPackageName = packageName;
+ return this;
+ }
+
+ public Builder setWakeLockTimeMs(long wakeLockTimeMs) {
+ mWakeLockTimeMs = wakeLockTimeMs;
+ return this;
+ }
+
+ public Anomaly build() {
+ return new Anomaly(this);
+ }
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragment.java b/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragment.java
new file mode 100644
index 00000000000..122b478e8d4
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragment.java
@@ -0,0 +1,103 @@
+/*
+ * 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 android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.support.annotation.VisibleForTesting;
+
+import com.android.settings.R;
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.fuelgauge.anomaly.action.AnomalyAction;
+
+/**
+ * Dialog Fragment to show action dialog for each anomaly
+ */
+public class AnomalyDialogFragment extends InstrumentedDialogFragment implements
+ DialogInterface.OnClickListener {
+
+ private static final String ARG_ANOMALY = "anomaly";
+
+ @VisibleForTesting
+ Anomaly mAnomaly;
+
+ /**
+ * Listener to give the control back to target fragment
+ */
+ public interface AnomalyDialogListener {
+ /**
+ * This method is invoked once anomaly is handled, then target fragment could do
+ * extra work. One example is that fragment could remove the anomaly preference
+ * since it has been handled
+ *
+ * @param anomaly that has been handled
+ */
+ void onAnomalyHandled(Anomaly anomaly);
+ }
+
+ public static AnomalyDialogFragment newInstance(Anomaly anomaly) {
+ AnomalyDialogFragment dialogFragment = new AnomalyDialogFragment();
+
+ Bundle args = new Bundle(1);
+ args.putParcelable(ARG_ANOMALY, anomaly);
+ dialogFragment.setArguments(args);
+
+ return dialogFragment;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ // TODO(b/37681923): add anomaly metric id
+ return 0;
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final AnomalyDialogListener lsn = (AnomalyDialogListener) getTargetFragment();
+ if (lsn == null) {
+ return;
+ }
+
+ final AnomalyAction anomalyAction = AnomalyUtils.getAnomalyAction(mAnomaly.type);
+ anomalyAction.handlePositiveAction(mAnomaly.packageName);
+ lsn.onAnomalyHandled(mAnomaly);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Bundle bundle = getArguments();
+ mAnomaly = bundle.getParcelable(ARG_ANOMALY);
+
+ final Context context = getContext();
+ final AnomalyAction anomalyAction = AnomalyUtils.getAnomalyAction(mAnomaly.type);
+ switch (anomalyAction.getActionType()) {
+ case Anomaly.AnomalyActionType.FORCE_STOP:
+ return new AlertDialog.Builder(context)
+ .setTitle(R.string.force_stop_dlg_title)
+ .setMessage(R.string.force_stop_dlg_text)
+ .setPositiveButton(R.string.dlg_ok, this)
+ .setNegativeButton(R.string.dlg_cancel, null)
+ .create();
+ default:
+ throw new IllegalArgumentException("unknown type " + mAnomaly.type);
+ }
+ }
+
+}
diff --git a/src/com/android/settings/fuelgauge/anomaly/AnomalyLoader.java b/src/com/android/settings/fuelgauge/anomaly/AnomalyLoader.java
new file mode 100644
index 00000000000..e689256a8c3
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/AnomalyLoader.java
@@ -0,0 +1,51 @@
+/*
+ * 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 android.content.Context;
+
+import com.android.internal.os.BatteryStatsHelper;
+import com.android.settings.fuelgauge.anomaly.checker.WakeLockAnomalyDetector;
+import com.android.settings.utils.AsyncLoader;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Loader to compute which apps are anomaly and return a anomaly list. It will return
+ * an empty list if there is no anomaly.
+ */
+//TODO(b/36924669): add test for this file, for now it seems there is nothing to test
+public class AnomalyLoader extends AsyncLoader> {
+ private BatteryStatsHelper mBatteryStatsHelper;
+
+ public AnomalyLoader(Context context, BatteryStatsHelper batteryStatsHelper) {
+ super(context);
+ mBatteryStatsHelper = batteryStatsHelper;
+ }
+
+ @Override
+ protected void onDiscardResult(List result) {}
+
+ @Override
+ public List loadInBackground() {
+ final List anomalies = new ArrayList<>();
+ anomalies.addAll(new WakeLockAnomalyDetector().detectAnomalies(mBatteryStatsHelper));
+
+ return anomalies;
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/anomaly/AnomalyPreferenceController.java b/src/com/android/settings/fuelgauge/anomaly/AnomalyPreferenceController.java
new file mode 100644
index 00000000000..b499690d686
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/AnomalyPreferenceController.java
@@ -0,0 +1,76 @@
+/*
+ * 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 android.support.annotation.VisibleForTesting;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v7.preference.Preference;
+
+import java.util.List;
+
+/**
+ * Manager that responsible for updating anomaly preference and handling preference click.
+ */
+public class AnomalyPreferenceController {
+ private static final String TAG = "AnomalyPreferenceController";
+ @VisibleForTesting
+ static final String ANOMALY_KEY = "high_usage";
+ private static final int REQUEST_ANOMALY_ACTION = 0;
+ private PreferenceFragment mFragment;
+ @VisibleForTesting
+ Preference mAnomalyPreference;
+ @VisibleForTesting
+ List mAnomalies;
+
+ public AnomalyPreferenceController(PreferenceFragment fragment) {
+ mFragment = fragment;
+ mAnomalyPreference = mFragment.getPreferenceScreen().findPreference(ANOMALY_KEY);
+ hideAnomalyPreference();
+ }
+
+ public boolean onPreferenceTreeClick(Preference preference) {
+ if (mAnomalies != null && ANOMALY_KEY.equals(preference.getKey())) {
+ if (mAnomalies.size() == 1) {
+ final Anomaly anomaly = mAnomalies.get(0);
+ AnomalyDialogFragment dialogFragment = AnomalyDialogFragment.newInstance(anomaly);
+ dialogFragment.setTargetFragment(mFragment, REQUEST_ANOMALY_ACTION);
+ dialogFragment.show(mFragment.getFragmentManager(), TAG);
+ } else {
+ //TODO(b/37681665): start a new fragment to handle it
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Update anomaly preference based on {@code anomalies}, also store a reference
+ * of {@paramref anomalies}, which would be used in {@link #onPreferenceTreeClick(Preference)}
+ *
+ * @param anomalies used to update the summary, this method will store a reference of it
+ */
+ public void updateAnomalyPreference(List anomalies) {
+ mAnomalies = anomalies;
+
+ mAnomalyPreference.setVisible(true);
+ //TODO(b/36924669): update summary for anomaly preference
+ }
+
+ public void hideAnomalyPreference() {
+ mAnomalyPreference.setVisible(false);
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java b/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java
new file mode 100644
index 00000000000..987482e1076
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java
@@ -0,0 +1,40 @@
+/*
+ * 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 com.android.settings.fuelgauge.anomaly.action.AnomalyAction;
+import com.android.settings.fuelgauge.anomaly.action.ForceStopAction;
+
+/**
+ * Utitily class for anomaly detection
+ */
+public class AnomalyUtils {
+
+ /**
+ * Return the corresponding {@link AnomalyAction} according to {@link AnomalyType}
+ *
+ * @return corresponding {@link AnomalyAction}, or null if cannot find it.
+ */
+ public static final AnomalyAction getAnomalyAction(int anomalyType) {
+ switch (anomalyType) {
+ case Anomaly.AnomalyType.WAKE_LOCK:
+ return new ForceStopAction();
+ default:
+ return null;
+ }
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/anomaly/action/AnomalyAction.java b/src/com/android/settings/fuelgauge/anomaly/action/AnomalyAction.java
new file mode 100644
index 00000000000..80137973d58
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/action/AnomalyAction.java
@@ -0,0 +1,25 @@
+/*
+ * 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.action;
+
+/**
+ * Interface for anomaly action, which is triggered if we need to handle the anomaly
+ */
+public interface AnomalyAction {
+ void handlePositiveAction(String packageName);
+ int getActionType();
+}
diff --git a/src/com/android/settings/fuelgauge/anomaly/action/ForceStopAction.java b/src/com/android/settings/fuelgauge/anomaly/action/ForceStopAction.java
new file mode 100644
index 00000000000..be5d6ab1958
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/action/ForceStopAction.java
@@ -0,0 +1,34 @@
+/*
+ * 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.action;
+
+import com.android.settings.fuelgauge.anomaly.Anomaly;
+
+/**
+ * Force stop action for anomaly app, which means to stop the app which causes anomaly
+ */
+public class ForceStopAction implements AnomalyAction {
+ @Override
+ public void handlePositiveAction(String packageName) {
+ // force stop the package
+ }
+
+ @Override
+ public int getActionType() {
+ return Anomaly.AnomalyActionType.FORCE_STOP;
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/anomaly/checker/AnomalyDetector.java b/src/com/android/settings/fuelgauge/anomaly/checker/AnomalyDetector.java
new file mode 100644
index 00000000000..b04ea660b8d
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/checker/AnomalyDetector.java
@@ -0,0 +1,27 @@
+/*
+ * 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.checker;
+
+import com.android.internal.os.BatteryStatsHelper;
+import com.android.settings.fuelgauge.anomaly.Anomaly;
+
+import java.util.List;
+
+public interface AnomalyDetector {
+ List detectAnomalies(BatteryStatsHelper batteryStatsHelper);
+}
diff --git a/src/com/android/settings/fuelgauge/anomaly/checker/WakeLockAnomalyDetector.java b/src/com/android/settings/fuelgauge/anomaly/checker/WakeLockAnomalyDetector.java
new file mode 100644
index 00000000000..a9ebc662dfd
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/checker/WakeLockAnomalyDetector.java
@@ -0,0 +1,36 @@
+/*
+ * 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.checker;
+
+import com.android.internal.os.BatteryStatsHelper;
+import com.android.settings.fuelgauge.anomaly.Anomaly;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Check whether apps holding wakelock too long
+ */
+public class WakeLockAnomalyDetector implements AnomalyDetector {
+
+ @Override
+ public List detectAnomalies(BatteryStatsHelper batteryStatsHelper) {
+ //TODO(b/36921529): check anomaly using the batteryStatsHelper
+ final List anomalies = new ArrayList<>();
+ return anomalies;
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragmentTest.java b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragmentTest.java
new file mode 100644
index 00000000000..95a8ff33cdc
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragmentTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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 static org.robolectric.Shadows.shadowOf;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+
+import com.android.settings.R;
+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.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowAlertDialog;
+import org.robolectric.shadows.ShadowDialog;
+import org.robolectric.util.FragmentTestUtil;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class AnomalyDialogFragmentTest {
+ @Anomaly.AnomalyType
+ private static final int ANOMALY_TYPE = Anomaly.AnomalyType.WAKE_LOCK;
+ private static final String PACKAGE_NAME = "com.android.app";
+ private static final int UID = 111;
+ private Anomaly mAnomaly;
+ private AnomalyDialogFragment mAnomalyDialogFragment;
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application;
+ mAnomaly = new Anomaly.Builder()
+ .setType(ANOMALY_TYPE)
+ .setUid(UID)
+ .setPackageName(PACKAGE_NAME)
+ .build();
+
+ mAnomalyDialogFragment = AnomalyDialogFragment.newInstance(mAnomaly);
+ }
+
+ @Test
+ public void testOnCreateDialog_hasCorrectData() {
+ FragmentTestUtil.startFragment(mAnomalyDialogFragment);
+
+ assertThat(mAnomalyDialogFragment.mAnomaly).isEqualTo(mAnomaly);
+ }
+
+ @Test
+ public void testOnCreateDialog_hasCorrectDialog() {
+ FragmentTestUtil.startFragment(mAnomalyDialogFragment);
+
+ final AlertDialog dialog = (AlertDialog) ShadowDialog.getLatestDialog();
+ ShadowAlertDialog shadowDialog = shadowOf(dialog);
+
+ assertThat(shadowDialog.getMessage()).isEqualTo(
+ mContext.getString(R.string.force_stop_dlg_text));
+ assertThat(shadowDialog.getTitle()).isEqualTo(
+ mContext.getString(R.string.force_stop_dlg_title));
+ assertThat(dialog.getButton(DialogInterface.BUTTON_POSITIVE).getText()).isEqualTo(
+ mContext.getString(R.string.dlg_ok));
+ assertThat(dialog.getButton(DialogInterface.BUTTON_NEGATIVE).getText()).isEqualTo(
+ mContext.getString(R.string.dlg_cancel));
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyPreferenceControllerTest.java
new file mode 100644
index 00000000000..f2e4482a7ae
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyPreferenceControllerTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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 static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+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.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;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+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 {
+ @Anomaly.AnomalyType
+ private static final int ANOMALY_TYPE = Anomaly.AnomalyType.WAKE_LOCK;
+ private static final String PACKAGE_NAME = "com.android.app";
+ private static final int UID = 111;
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private PreferenceFragment mFragment;
+ @Mock
+ private FragmentManager mFragmentManager;
+ @Mock
+ private FragmentTransaction mFragmentTransaction;
+ private AnomalyPreferenceController mAnomalyPreferenceController;
+ private Preference mPreference;
+ private Context mContext;
+ private List mAnomalyList;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = RuntimeEnvironment.application;
+ mPreference = new Preference(mContext);
+ mPreference.setKey(AnomalyPreferenceController.ANOMALY_KEY);
+ when(mFragment.getPreferenceManager().findPreference(any())).thenReturn(mPreference);
+ when(mFragment.getFragmentManager()).thenReturn(mFragmentManager);
+ when(mFragmentManager.beginTransaction()).thenReturn(mFragmentTransaction);
+
+ mAnomalyList = new ArrayList<>();
+ Anomaly anomaly = new Anomaly.Builder()
+ .setType(ANOMALY_TYPE)
+ .setUid(UID)
+ .setPackageName(PACKAGE_NAME)
+ .build();
+ mAnomalyList.add(anomaly);
+
+ mAnomalyPreferenceController = new AnomalyPreferenceController(mFragment);
+ }
+
+ @Test
+ public void testUpdateAnomalyPreference_hasCorrectData() {
+ mAnomalyPreferenceController.updateAnomalyPreference(mAnomalyList);
+
+ //add more test when this method is complete
+ assertThat(mAnomalyPreferenceController.mAnomalies).isEqualTo(mAnomalyList);
+ }
+
+ @Test
+ public void testOnPreferenceTreeClick_oneAnomaly_showDialog() {
+ mAnomalyPreferenceController.mAnomalies = mAnomalyList;
+
+ mAnomalyPreferenceController.onPreferenceTreeClick(mPreference);
+
+ verify(mFragmentManager).beginTransaction();
+ verify(mFragmentTransaction).add(any(), anyString());
+ verify(mFragmentTransaction).commit();
+ }
+
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyTest.java b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyTest.java
new file mode 100644
index 00000000000..b57c7eb8a0e
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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 com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class AnomalyTest {
+ private static int TYPE = Anomaly.AnomalyType.WAKE_LOCK;
+ private static int UID = 111;
+ private static long WAKE_LOCK_TIME_MS = 1500;
+ private static String PACKAGE_NAME = "com.android.settings";
+ private static String DISPLAY_NAME = "settings";
+
+ @Test
+ public void testBuilder_buildCorrectly() {
+ Anomaly anomaly = new Anomaly.Builder()
+ .setType(TYPE)
+ .setUid(UID)
+ .setWakeLockTimeMs(WAKE_LOCK_TIME_MS)
+ .setPackageName(PACKAGE_NAME)
+ .setDisplayName(DISPLAY_NAME)
+ .build();
+
+ assertThat(anomaly.type).isEqualTo(TYPE);
+ assertThat(anomaly.uid).isEqualTo(UID);
+ assertThat(anomaly.wakelockTimeMs).isEqualTo(WAKE_LOCK_TIME_MS);
+ assertThat(anomaly.packageName).isEqualTo(PACKAGE_NAME);
+ assertThat(anomaly.displayName).isEqualTo(DISPLAY_NAME);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyUtilsTest.java
new file mode 100644
index 00000000000..8d6d31baece
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyUtilsTest.java
@@ -0,0 +1,38 @@
+/*
+ * 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 com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.fuelgauge.anomaly.action.ForceStopAction;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class AnomalyUtilsTest {
+
+ @Test
+ public void testGetAnomalyAction_typeWakeLock_returnForceStop() {
+ assertThat(AnomalyUtils.getAnomalyAction(Anomaly.AnomalyType.WAKE_LOCK)).isInstanceOf(
+ ForceStopAction.class);
+ }
+}