Framework for the anomaly detection.

This cl adds the following components for anomaly dection:
1. AnomalyLoader: run all the anomaly checks in the background
2. Anomaly: Data class to represent what anomaly it is
3. Detector: Different kinds of anomaly detector with common interface
4. Action:  Suggestions when facing anomaly(Force stop, uninstall)
5. AnomalyDialogFragment: show the confirm dialog for action
6. AnomalyPreferenceController: handle update and click for
anomalyPreference, since it will be used in multiple fragments.
7. AnomalyUtils: utility class for anomaly

This cl also adds skeleton for the wakelock check and action. Following
cls will add real implementation about it.

Bug: 36924669
Test: RunSettingsRoboTests
Change-Id: I89fc4b6963757869b93791b4275ca53c04ab9604
This commit is contained in:
jackqdyulei
2017-04-25 11:30:06 -07:00
parent 4869effb72
commit df6dd14799
15 changed files with 875 additions and 1 deletions

View File

@@ -25,6 +25,9 @@
android:selectable="true" android:selectable="true"
android:layout="@layout/battery_header"/> android:layout="@layout/battery_header"/>
<Preference
android:key="high_usage"/>
<PreferenceCategory <PreferenceCategory
android:key="device_usage_list"> android:key="device_usage_list">

View File

@@ -17,9 +17,11 @@
package com.android.settings.fuelgauge; package com.android.settings.fuelgauge;
import android.app.Activity; import android.app.Activity;
import android.app.LoaderManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.Loader;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.BatteryStats; import android.os.BatteryStats;
import android.os.Build; 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.AutoBrightnessPreferenceController;
import com.android.settings.display.BatteryPercentagePreferenceController; import com.android.settings.display.BatteryPercentagePreferenceController;
import com.android.settings.display.TimeoutPreferenceController; 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.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.widget.FooterPreferenceMixin; 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 * Displays a list of apps and subsystems that consume power, ordered by how much power was
* consumed since the last time it was unplugged. * 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"; 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 String KEY_BATTERY_HEADER = "battery_header";
private static final int MAX_ITEMS_TO_LIST = USE_FAKE_DATA ? 30 : 10; 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 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_SCREEN_USAGE = "screen_usage";
private static final String KEY_TIME_SINCE_LAST_FULL_CHARGE = "last_full_charge"; 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 LayoutPreference mBatteryLayoutPref;
private PreferenceGroup mAppListGroup; private PreferenceGroup mAppListGroup;
private AnomalyPreferenceController mAnomalyPreferenceController;
private int mStatsType = BatteryStats.STATS_SINCE_CHARGED; private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;
private LoaderManager.LoaderCallbacks<List<Anomaly>> mAnomalyLoaderCallbacks =
new LoaderManager.LoaderCallbacks<List<Anomaly>>() {
@Override
public Loader<List<Anomaly>> onCreateLoader(int id, Bundle args) {
return new AnomalyLoader(getContext(), mStatsHelper);
}
@Override
public void onLoadFinished(Loader<List<Anomaly>> loader, List<Anomaly> data) {
// show high usage preference if possible
mAnomalyPreferenceController.updateAnomalyPreference(data);
}
@Override
public void onLoaderReset(Loader<List<Anomaly>> loader) {
}
};
@Override @Override
public void onCreate(Bundle icicle) { public void onCreate(Bundle icicle) {
super.onCreate(icicle); super.onCreate(icicle);
@@ -130,6 +159,7 @@ public class PowerUsageSummary extends PowerUsageBase {
mLastFullChargePref = (PowerGaugePreference) findPreference( mLastFullChargePref = (PowerGaugePreference) findPreference(
KEY_TIME_SINCE_LAST_FULL_CHARGE); KEY_TIME_SINCE_LAST_FULL_CHARGE);
mFooterPreferenceMixin.createFooterPreference().setTitle(R.string.battery_footer_summary); mFooterPreferenceMixin.createFooterPreference().setTitle(R.string.battery_footer_summary);
mAnomalyPreferenceController = new AnomalyPreferenceController(this);
mBatteryUtils = BatteryUtils.getInstance(getContext()); mBatteryUtils = BatteryUtils.getInstance(getContext());
@@ -163,6 +193,9 @@ public class PowerUsageSummary extends PowerUsageBase {
@Override @Override
public boolean onPreferenceTreeClick(Preference preference) { public boolean onPreferenceTreeClick(Preference preference) {
if (mAnomalyPreferenceController.onPreferenceTreeClick(preference)) {
return true;
}
if (KEY_BATTERY_HEADER.equals(preference.getKey())) { if (KEY_BATTERY_HEADER.equals(preference.getKey())) {
performBatteryHeaderClick(); performBatteryHeaderClick();
return true; return true;
@@ -403,6 +436,8 @@ public class PowerUsageSummary extends PowerUsageBase {
return; return;
} }
getLoaderManager().initLoader(ANOMALY_LOADER, null, mAnomalyLoaderCallbacks);
cacheRemoveAllPrefs(mAppListGroup); cacheRemoveAllPrefs(mAppListGroup);
mAppListGroup.setOrderingAsAdded(false); mAppListGroup.setOrderingAsAdded(false);
boolean addedSome = 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 static class SummaryProvider implements SummaryLoader.SummaryProvider {
private final Context mContext; private final Context mContext;
private final SummaryLoader mLoader; private final SummaryLoader mLoader;

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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<List<Anomaly>> {
private BatteryStatsHelper mBatteryStatsHelper;
public AnomalyLoader(Context context, BatteryStatsHelper batteryStatsHelper) {
super(context);
mBatteryStatsHelper = batteryStatsHelper;
}
@Override
protected void onDiscardResult(List<Anomaly> result) {}
@Override
public List<Anomaly> loadInBackground() {
final List<Anomaly> anomalies = new ArrayList<>();
anomalies.addAll(new WakeLockAnomalyDetector().detectAnomalies(mBatteryStatsHelper));
return anomalies;
}
}

View File

@@ -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<Anomaly> 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<Anomaly> anomalies) {
mAnomalies = anomalies;
mAnomalyPreference.setVisible(true);
//TODO(b/36924669): update summary for anomaly preference
}
public void hideAnomalyPreference() {
mAnomalyPreference.setVisible(false);
}
}

View File

@@ -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;
}
}
}

View File

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

View File

@@ -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;
}
}

View File

@@ -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<Anomaly> detectAnomalies(BatteryStatsHelper batteryStatsHelper);
}

View File

@@ -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<Anomaly> detectAnomalies(BatteryStatsHelper batteryStatsHelper) {
//TODO(b/36921529): check anomaly using the batteryStatsHelper
final List<Anomaly> anomalies = new ArrayList<>();
return anomalies;
}
}

View File

@@ -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));
}
}

View File

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

View File

@@ -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);
}
}

View File

@@ -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);
}
}