From 4cb19e74b392ce9fc114ce1645699a04049954bc Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Mon, 1 May 2017 13:03:49 -0700 Subject: [PATCH] Add anomaly icon for PowerGaugePreference When the app contains anomaly, we should show anomaly icon(triangle alert). This cl also extracts a new method refreshAppListGroup from refreshUi. New method is used to only refresh appListGroup. Reason for this action: 1. Improve performance(partial refresh) 2. We init AnomalyLoader in refreshUi, invoke refreshUi in onLoadFinish will create infinite loop. Bug: 36924669 Test: RunSettingsRoboTests Change-Id: If54c89349e21763ca714123ac6ae884bd0f6a377 --- res/layout/preference_widget_summary.xml | 2 + .../fuelgauge/PowerGaugePreference.java | 14 +++ .../settings/fuelgauge/PowerUsageSummary.java | 58 +++++++--- .../fuelgauge/PowerGaugePreferenceTest.java | 103 ++++++++++++++++++ .../fuelgauge/PowerUsageSummaryTest.java | 22 ++++ 5 files changed, 185 insertions(+), 14 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/PowerGaugePreferenceTest.java diff --git a/res/layout/preference_widget_summary.xml b/res/layout/preference_widget_summary.xml index 2d7ed1d2bea..fae50273053 100644 --- a/res/layout/preference_widget_summary.xml +++ b/res/layout/preference_widget_summary.xml @@ -18,6 +18,8 @@ android:id="@+id/widget_summary" android:layout_width="match_parent" android:layout_height="wrap_content" + android:drawablePadding="8dp" + android:gravity="center_vertical|end" android:textAlignment="viewEnd" android:textAppearance="?android:attr/textAppearanceSmall" android:textColor="?android:attr/textColorSecondary" /> diff --git a/src/com/android/settings/fuelgauge/PowerGaugePreference.java b/src/com/android/settings/fuelgauge/PowerGaugePreference.java index b124c8150b0..2c12f717b6d 100644 --- a/src/com/android/settings/fuelgauge/PowerGaugePreference.java +++ b/src/com/android/settings/fuelgauge/PowerGaugePreference.java @@ -21,6 +21,7 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.support.v7.preference.PreferenceViewHolder; import android.util.AttributeSet; +import android.view.View; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -41,6 +42,7 @@ public class PowerGaugePreference extends TintablePreference { private BatteryEntry mInfo; private CharSequence mContentDescription; private CharSequence mProgress; + private boolean mShowAnomalyIcon; public PowerGaugePreference(Context context, Drawable icon, CharSequence contentDescription, BatteryEntry info) { @@ -63,6 +65,7 @@ public class PowerGaugePreference extends TintablePreference { mInfo = info; mContentDescription = contentDescription; mIconSize = context.getResources().getDimensionPixelSize(R.dimen.app_icon_size); + mShowAnomalyIcon = false; } public void setContentDescription(String name) { @@ -88,6 +91,11 @@ public class PowerGaugePreference extends TintablePreference { return mProgress; } + public void shouldShowAnomalyIcon(boolean showAnomalyIcon) { + mShowAnomalyIcon = showAnomalyIcon; + notifyChanged(); + } + BatteryEntry getInfo() { return mInfo; } @@ -98,6 +106,12 @@ public class PowerGaugePreference extends TintablePreference { ImageView icon = (ImageView) view.findViewById(android.R.id.icon); icon.setLayoutParams(new LinearLayout.LayoutParams(mIconSize, mIconSize)); + final TextView subtitle = (TextView) view.findViewById(R.id.widget_summary); + subtitle.setText(mProgress); + if (mShowAnomalyIcon) { + subtitle.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_warning_24dp, 0, + 0, 0); + } ((TextView) view.findViewById(R.id.widget_summary)).setText(mProgress); if (mContentDescription != null) { final TextView titleView = (TextView) view.findViewById(android.R.id.title); diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java index ac243f314e9..0ac0c6008a2 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java +++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java @@ -22,6 +22,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.Loader; +import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.os.BatteryStats; import android.os.Build; @@ -122,6 +123,11 @@ public class PowerUsageSummary extends PowerUsageBase implements PowerUsageFeatureProvider mPowerFeatureProvider; @VisibleForTesting BatteryUtils mBatteryUtils; + /** + * SparseArray that maps uid to {@link Anomaly}, so we could find {@link Anomaly} by uid + */ + @VisibleForTesting + SparseArray> mAnomalySparseArray; private LayoutPreference mBatteryLayoutPref; private PreferenceGroup mAppListGroup; @@ -140,6 +146,9 @@ public class PowerUsageSummary extends PowerUsageBase implements public void onLoadFinished(Loader> loader, List data) { // show high usage preference if possible mAnomalySummaryPreferenceController.updateAnomalySummaryPreference(data); + + updateAnomalySparseArray(data); + refreshAppListGroup(); } @Override @@ -162,6 +171,7 @@ public class PowerUsageSummary extends PowerUsageBase implements mAnomalySummaryPreferenceController = new AnomalySummaryPreferenceController( (SettingsActivity) getActivity(), this); mBatteryUtils = BatteryUtils.getInstance(getContext()); + mAnomalySparseArray = new SparseArray<>(); initFeatureProvider(); } @@ -438,14 +448,6 @@ public class PowerUsageSummary extends PowerUsageBase implements getLoaderManager().initLoader(ANOMALY_LOADER, null, mAnomalyLoaderCallbacks); - cacheRemoveAllPrefs(mAppListGroup); - mAppListGroup.setOrderingAsAdded(false); - boolean addedSome = false; - - final PowerProfile powerProfile = mStatsHelper.getPowerProfile(); - final BatteryStats stats = mStatsHelper.getStats(); - final double averagePower = powerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL); - final long elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000; Intent batteryBroadcast = context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); @@ -453,12 +455,6 @@ public class PowerUsageSummary extends PowerUsageBase implements mStatsHelper.getStats(), elapsedRealtimeUs, false); updateHeaderPreference(batteryInfo); - final TypedValue value = new TypedValue(); - context.getTheme().resolveAttribute(android.R.attr.colorControlNormal, value, true); - final int colorControl = context.getColor(value.resourceId); - final int dischargeAmount = USE_FAKE_DATA ? 5000 - : stats != null ? stats.getDischargeAmount(mStatsType) : 0; - final long runningTime = calculateRunningTimeBasedOnStatsType(); updateScreenPreference(); updateLastFullChargePreference(runningTime); @@ -467,6 +463,27 @@ public class PowerUsageSummary extends PowerUsageBase implements mAppListGroup.setTitle( TextUtils.expandTemplate(getText(R.string.power_usage_list_summary), timeSequence)); + refreshAppListGroup(); + } + + private void refreshAppListGroup() { + final Context context = getContext(); + final PowerProfile powerProfile = mStatsHelper.getPowerProfile(); + final BatteryStats stats = mStatsHelper.getStats(); + final double averagePower = powerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL); + boolean addedSome = false; + + TypedArray array = context.obtainStyledAttributes( + new int[]{android.R.attr.colorControlNormal}); + final int colorControl = array.getColor(0, 0); + array.recycle(); + + final int dischargeAmount = USE_FAKE_DATA ? 5000 + : stats != null ? stats.getDischargeAmount(mStatsType) : 0; + + cacheRemoveAllPrefs(mAppListGroup); + mAppListGroup.setOrderingAsAdded(false); + if (averagePower >= MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP || USE_FAKE_DATA) { final List usageList = getCoalescedUsageList( USE_FAKE_DATA ? getFakeStats() : mStatsHelper.getUsageList()); @@ -532,6 +549,7 @@ public class PowerUsageSummary extends PowerUsageBase implements pref.setTitle(entry.getLabel()); pref.setOrder(i + 1); pref.setPercent(percentOfTotal); + pref.shouldShowAnomalyIcon(mAnomalySparseArray.get(sipper.getUid()) != null); if (sipper.usageTimeMs == 0 && sipper.drainType == DrainType.APP) { sipper.usageTimeMs = mBatteryUtils.getProcessTimeMs( BatteryUtils.StatusType.FOREGROUND, sipper.uidObj, mStatsType); @@ -659,6 +677,18 @@ public class PowerUsageSummary extends PowerUsageBase implements .getPowerUsageFeatureProvider(context); } + @VisibleForTesting + void updateAnomalySparseArray(List anomalies) { + mAnomalySparseArray.clear(); + for (int i = 0, size = anomalies.size(); i < size; i++) { + final Anomaly anomaly = anomalies.get(i); + if (mAnomalySparseArray.get(anomaly.uid) == null) { + mAnomalySparseArray.append(anomaly.uid, new ArrayList<>()); + } + mAnomalySparseArray.get(anomaly.uid).add(anomaly); + } + } + private static List getFakeStats() { ArrayList stats = new ArrayList<>(); float use = 5; diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerGaugePreferenceTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerGaugePreferenceTest.java new file mode 100644 index 00000000000..39c168e3f55 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerGaugePreferenceTest.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; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.VectorDrawable; +import android.os.PowerManager; +import android.support.v7.preference.PreferenceViewHolder; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.settings.R; +import com.android.settings.SettingsRobolectricTestRunner; +import com.android.settings.TestConfig; +import com.android.settings.core.lifecycle.Lifecycle; +import com.android.settings.widget.MasterSwitchPreference; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class PowerGaugePreferenceTest { + private static final String SUBTITLE = "Summary"; + private static final String CONTENT_DESCRIPTION = "Content description"; + private Context mContext; + private PowerGaugePreference mPowerGaugePreference; + private View mRootView; + private View mWidgetView; + private PreferenceViewHolder mPreferenceViewHolder; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext = RuntimeEnvironment.application; + mRootView = LayoutInflater.from(mContext).inflate(R.layout.preference_material_settings, + null); + mWidgetView = LayoutInflater.from(mContext).inflate(R.layout.preference_widget_summary, + null); + ((LinearLayout) mRootView.findViewById(android.R.id.widget_frame)).addView(mWidgetView); + mPreferenceViewHolder = PreferenceViewHolder.createInstanceForTests(mRootView); + + mPowerGaugePreference = new PowerGaugePreference(mContext); + } + + @Test + public void testOnBindViewHolder_bindSubtitle() { + mPowerGaugePreference.setSubtitle(SUBTITLE); + mPowerGaugePreference.onBindViewHolder(mPreferenceViewHolder); + + assertThat(((TextView) mPreferenceViewHolder.findViewById( + R.id.widget_summary)).getText()).isEqualTo(SUBTITLE); + } + + @Test + public void testOnBindViewHolder_bindAnomalyIcon() { + mPowerGaugePreference.shouldShowAnomalyIcon(true); + mPowerGaugePreference.onBindViewHolder(mPreferenceViewHolder); + + final Drawable[] drawables = ((TextView) mPreferenceViewHolder.findViewById( + R.id.widget_summary)).getCompoundDrawablesRelative(); + + assertThat(drawables[0]).isInstanceOf(VectorDrawable.class); + } + + @Test + public void testOnBindViewHolder_bindContentDescription() { + mPowerGaugePreference.setContentDescription(CONTENT_DESCRIPTION); + mPowerGaugePreference.onBindViewHolder(mPreferenceViewHolder); + + assertThat(mPreferenceViewHolder.findViewById(android.R.id.title).getContentDescription()) + .isEqualTo(CONTENT_DESCRIPTION); + } +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java index 3556d09a53f..24dbfe0a97b 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java @@ -21,6 +21,7 @@ import android.os.PowerManager; import android.os.Process; import android.text.TextUtils; import android.text.format.DateUtils; +import android.util.SparseArray; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -37,6 +38,7 @@ import com.android.settings.TestConfig; import com.android.settings.Utils; import com.android.settings.applications.LayoutPreference; import com.android.settings.core.PreferenceController; +import com.android.settings.fuelgauge.anomaly.Anomaly; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.shadow.SettingsShadowResources; import com.android.settings.testutils.shadow.ShadowDynamicIndexableContentMonitor; @@ -91,6 +93,7 @@ public class PowerUsageSummaryTest { private static final String STUB_STRING = "stub_string"; private static final int BATTERY_LEVEL = 55; private static final int UID = 123; + private static final int UID_2 = 234; private static final int POWER_MAH = 100; private static final long REMAINING_TIME_US = 100000; private static final long TIME_SINCE_LAST_FULL_CHARGE_MS = 120 * 60 * 1000; @@ -148,6 +151,7 @@ public class PowerUsageSummaryTest { private PowerGaugePreference mPreference; private PowerGaugePreference mScreenUsagePref; private PowerGaugePreference mLastFullChargePref; + private SparseArray> mAnomalySparseArray; @Before public void setUp() { @@ -464,6 +468,24 @@ public class PowerUsageSummaryTest { assertThat(preferenceScreenKeys).containsAllIn(preferenceKeys); } + @Test + public void testUpdateAnomalySparseArray() { + mFragment.mAnomalySparseArray = new SparseArray<>(); + final List anomalies = new ArrayList<>(); + final Anomaly anomaly1 = new Anomaly.Builder().setUid(UID).build(); + final Anomaly anomaly2 = new Anomaly.Builder().setUid(UID).build(); + final Anomaly anomaly3 = new Anomaly.Builder().setUid(UID_2).build(); + anomalies.add(anomaly1); + anomalies.add(anomaly2); + anomalies.add(anomaly3); + + mFragment.updateAnomalySparseArray(anomalies); + + assertThat(mFragment.mAnomalySparseArray.get(UID)).containsExactly(anomaly1, anomaly2); + assertThat(mFragment.mAnomalySparseArray.get(UID_2)).containsExactly(anomaly3); + } + + public static class TestFragment extends PowerUsageSummary { private Context mContext;