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