From c85496297a17f5d97664561452240163bc50a7fa Mon Sep 17 00:00:00 2001 From: Beverly Date: Fri, 16 Feb 2018 14:33:37 -0500 Subject: [PATCH 01/17] System gets its own dnd toggle Test: make ROBOTEST_FILTER=ZenModeSystemPreferenceControllerTest RunSettingsRoboTests -j40 Bug: 73362490 Change-Id: Id5212665da8c820fc070676ff034dbe67510ea5d --- res/values/strings.xml | 6 +- res/xml/zen_mode_behavior_settings.xml | 10 +- .../notification/ZenModeBehaviorSettings.java | 5 +- ... => ZenModeMediaPreferenceController.java} | 10 +- .../notification/ZenModeSettings.java | 15 +- .../ZenModeSystemPreferenceController.java | 81 +++++++++++ .../ZenModeMediaPreferenceControllerTest.java | 17 +-- .../notification/ZenModeSettingsTest.java | 4 +- ...ZenModeSystemPreferenceControllerTest.java | 135 ++++++++++++++++++ 9 files changed, 253 insertions(+), 30 deletions(-) rename src/com/android/settings/notification/{ZenModeMediaSystemOtherPreferenceController.java => ZenModeMediaPreferenceController.java} (84%) create mode 100644 src/com/android/settings/notification/ZenModeSystemPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/notification/ZenModeSystemPreferenceControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 4e81b7a129c..05448f8557b 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -7528,10 +7528,10 @@ Alarms - Media + Media - - Includes system feedback like touch and charging sounds + + Touch and charging sounds Reminders diff --git a/res/xml/zen_mode_behavior_settings.xml b/res/xml/zen_mode_behavior_settings.xml index 8d2c28c7d7d..084b78d5c51 100644 --- a/res/xml/zen_mode_behavior_settings.xml +++ b/res/xml/zen_mode_behavior_settings.xml @@ -20,7 +20,7 @@ xmlns:settings="http://schemas.android.com/apk/res-auto" android:key="zen_mode_behavior_settings_page" android:title="@string/zen_mode_behavior_settings_title" - settings:initialExpandedChildrenCount="7"> + settings:initialExpandedChildrenCount="8"> + android:title="@string/zen_mode_media"/> + + + controllers = new ArrayList<>(); controllers.add(new ZenModeAlarmsPreferenceController(context, lifecycle)); - controllers.add(new ZenModeMediaSystemOtherPreferenceController(context, lifecycle)); + controllers.add(new ZenModeMediaPreferenceController(context, lifecycle)); + controllers.add(new ZenModeSystemPreferenceController(context, lifecycle)); controllers.add(new ZenModeEventsPreferenceController(context, lifecycle)); controllers.add(new ZenModeRemindersPreferenceController(context, lifecycle)); controllers.add(new ZenModeMessagesPreferenceController(context, lifecycle)); @@ -85,7 +86,7 @@ public class ZenModeBehaviorSettings extends ZenModeSettingsBase implements Inde public List getNonIndexableKeys(Context context) { final List keys = super.getNonIndexableKeys(context); keys.add(ZenModeAlarmsPreferenceController.KEY); - keys.add(ZenModeMediaSystemOtherPreferenceController.KEY); + keys.add(ZenModeMediaPreferenceController.KEY); keys.add(ZenModeEventsPreferenceController.KEY); keys.add(ZenModeRemindersPreferenceController.KEY); keys.add(ZenModeMessagesPreferenceController.KEY); diff --git a/src/com/android/settings/notification/ZenModeMediaSystemOtherPreferenceController.java b/src/com/android/settings/notification/ZenModeMediaPreferenceController.java similarity index 84% rename from src/com/android/settings/notification/ZenModeMediaSystemOtherPreferenceController.java rename to src/com/android/settings/notification/ZenModeMediaPreferenceController.java index 8afe881e434..12cedf26d24 100644 --- a/src/com/android/settings/notification/ZenModeMediaSystemOtherPreferenceController.java +++ b/src/com/android/settings/notification/ZenModeMediaPreferenceController.java @@ -25,13 +25,13 @@ import android.util.Log; import com.android.settingslib.core.lifecycle.Lifecycle; -public class ZenModeMediaSystemOtherPreferenceController extends AbstractZenModePreferenceController +public class ZenModeMediaPreferenceController extends AbstractZenModePreferenceController implements Preference.OnPreferenceChangeListener { protected static final String KEY = "zen_mode_media"; private final ZenModeBackend mBackend; - public ZenModeMediaSystemOtherPreferenceController(Context context, Lifecycle lifecycle) { + public ZenModeMediaPreferenceController(Context context, Lifecycle lifecycle) { super(context, KEY, lifecycle); mBackend = ZenModeBackend.getInstance(context); } @@ -63,7 +63,7 @@ public class ZenModeMediaSystemOtherPreferenceController extends AbstractZenMode default: pref.setEnabled(true); pref.setChecked(mBackend.isPriorityCategoryEnabled( - Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER)); + Policy.PRIORITY_CATEGORY_MEDIA)); } } @@ -71,9 +71,9 @@ public class ZenModeMediaSystemOtherPreferenceController extends AbstractZenMode public boolean onPreferenceChange(Preference preference, Object newValue) { final boolean allowMedia = (Boolean) newValue; if (ZenModeSettingsBase.DEBUG) { - Log.d(TAG, "onPrefChange allowMediaSystemOther=" + allowMedia); + Log.d(TAG, "onPrefChange allowMedia=" + allowMedia); } - mBackend.saveSoundPolicy(Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER, allowMedia); + mBackend.saveSoundPolicy(Policy.PRIORITY_CATEGORY_MEDIA, allowMedia); return true; } } diff --git a/src/com/android/settings/notification/ZenModeSettings.java b/src/com/android/settings/notification/ZenModeSettings.java index 272bb416588..4e8793befc8 100644 --- a/src/com/android/settings/notification/ZenModeSettings.java +++ b/src/com/android/settings/notification/ZenModeSettings.java @@ -77,9 +77,11 @@ public class ZenModeSettings extends ZenModeSettingsBase { mContext = context; } + // these should match NotificationManager.Policy#ALL_PRIORITY_CATEGORIES private static final int[] ALL_PRIORITY_CATEGORIES = { Policy.PRIORITY_CATEGORY_ALARMS, - Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER, + Policy.PRIORITY_CATEGORY_MEDIA, + Policy.PRIORITY_CATEGORY_SYSTEM, Policy.PRIORITY_CATEGORY_REMINDERS, Policy.PRIORITY_CATEGORY_EVENTS, Policy.PRIORITY_CATEGORY_MESSAGES, @@ -104,10 +106,10 @@ public class ZenModeSettings extends ZenModeSettingsBase { return mContext.getString(R.string.zen_mode_behavior_total_silence); } - // only alarms and media/system can bypass dnd + // only alarms and media can bypass dnd if (numCategories == 2 && isCategoryEnabled(policy, Policy.PRIORITY_CATEGORY_ALARMS) && - isCategoryEnabled(policy, Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER)) { + isCategoryEnabled(policy, Policy.PRIORITY_CATEGORY_MEDIA)) { return mContext.getString(R.string.zen_mode_behavior_alarms_only); } @@ -164,9 +166,12 @@ public class ZenModeSettings extends ZenModeSettingsBase { if (isCategoryEnabled(policy, category)) { if (category == Policy.PRIORITY_CATEGORY_ALARMS) { enabledCategories.add(mContext.getString(R.string.zen_mode_alarms)); - } else if (category == Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) { + } else if (category == Policy.PRIORITY_CATEGORY_MEDIA) { enabledCategories.add(mContext.getString( - R.string.zen_mode_media_system_other)); + R.string.zen_mode_media)); + } else if (category == Policy.PRIORITY_CATEGORY_SYSTEM) { + enabledCategories.add(mContext.getString( + R.string.zen_mode_system)); } else if (category == Policy.PRIORITY_CATEGORY_REMINDERS) { enabledCategories.add(mContext.getString(R.string.zen_mode_reminders)); } else if (category == Policy.PRIORITY_CATEGORY_EVENTS) { diff --git a/src/com/android/settings/notification/ZenModeSystemPreferenceController.java b/src/com/android/settings/notification/ZenModeSystemPreferenceController.java new file mode 100644 index 00000000000..364c829bb35 --- /dev/null +++ b/src/com/android/settings/notification/ZenModeSystemPreferenceController.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2018 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.notification; + +import android.app.NotificationManager.Policy; +import android.content.Context; +import android.provider.Settings; +import android.support.v14.preference.SwitchPreference; +import android.support.v7.preference.Preference; +import android.util.Log; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settingslib.core.lifecycle.Lifecycle; + +public class ZenModeSystemPreferenceController extends + AbstractZenModePreferenceController implements Preference.OnPreferenceChangeListener { + + protected static final String KEY = "zen_mode_system"; + + public ZenModeSystemPreferenceController(Context context, Lifecycle lifecycle) { + super(context, KEY, lifecycle); + } + + @Override + public String getPreferenceKey() { + return KEY; + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + + SwitchPreference pref = (SwitchPreference) preference; + switch (getZenMode()) { + case Settings.Global.ZEN_MODE_NO_INTERRUPTIONS: + pref.setEnabled(false); + pref.setChecked(false); + break; + case Settings.Global.ZEN_MODE_ALARMS: + pref.setEnabled(false); + pref.setChecked(false); + break; + default: + pref.setEnabled(true); + pref.setChecked(mBackend.isPriorityCategoryEnabled( + Policy.PRIORITY_CATEGORY_SYSTEM)); + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + final boolean allowSystem = (Boolean) newValue; + if (ZenModeSettingsBase.DEBUG) { + Log.d(TAG, "onPrefChange allowSystem=" + allowSystem); + } + + mMetricsFeatureProvider.action(mContext, MetricsProto.MetricsEvent.ACTION_ZEN_ALLOW_SYSTEM, + allowSystem); + mBackend.saveSoundPolicy(Policy.PRIORITY_CATEGORY_SYSTEM, allowSystem); + return true; + } +} diff --git a/tests/robotests/src/com/android/settings/notification/ZenModeMediaPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/ZenModeMediaPreferenceControllerTest.java index db0a2e900bc..db828a296b7 100644 --- a/tests/robotests/src/com/android/settings/notification/ZenModeMediaPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/ZenModeMediaPreferenceControllerTest.java @@ -46,10 +46,7 @@ import org.robolectric.util.ReflectionHelpers; @RunWith(SettingsRobolectricTestRunner.class) public class ZenModeMediaPreferenceControllerTest { - - private static final boolean MEDIA_SETTINGS = true; - - private ZenModeMediaSystemOtherPreferenceController mController; + private ZenModeMediaPreferenceController mController; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mContext; @@ -75,7 +72,7 @@ public class ZenModeMediaPreferenceControllerTest { mContentResolver = RuntimeEnvironment.application.getContentResolver(); when(mNotificationManager.getNotificationPolicy()).thenReturn(mPolicy); - mController = new ZenModeMediaSystemOtherPreferenceController(mContext, + mController = new ZenModeMediaPreferenceController(mContext, mock(Lifecycle.class)); ReflectionHelpers.setField(mController, "mBackend", mBackend); @@ -111,13 +108,13 @@ public class ZenModeMediaPreferenceControllerTest { Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_IMPORTANT_INTERRUPTIONS); when(mBackend.isPriorityCategoryEnabled( - NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER)). - thenReturn(MEDIA_SETTINGS); + NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA)). + thenReturn(true); mController.updateState(mockPref); verify(mockPref).setEnabled(true); - verify(mockPref).setChecked(MEDIA_SETTINGS); + verify(mockPref).setChecked(true); } @Test @@ -126,7 +123,7 @@ public class ZenModeMediaPreferenceControllerTest { mController.onPreferenceChange(mockPref, allow); verify(mBackend).saveSoundPolicy( - NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER, allow); + NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA, allow); } @Test @@ -135,6 +132,6 @@ public class ZenModeMediaPreferenceControllerTest { mController.onPreferenceChange(mockPref, allow); verify(mBackend).saveSoundPolicy( - NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER, allow); + NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA, allow); } } \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/notification/ZenModeSettingsTest.java b/tests/robotests/src/com/android/settings/notification/ZenModeSettingsTest.java index 66b11a69cfa..5c81efc9468 100644 --- a/tests/robotests/src/com/android/settings/notification/ZenModeSettingsTest.java +++ b/tests/robotests/src/com/android/settings/notification/ZenModeSettingsTest.java @@ -52,7 +52,7 @@ public class ZenModeSettingsTest { NotificationManager.Policy.PRIORITY_CATEGORY_EVENTS | NotificationManager.Policy.PRIORITY_CATEGORY_REMINDERS | NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS - | NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER, + | NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA, 0, 0); final String result = mBuilder.getBehaviorSettingSummary(policy, Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS); @@ -75,7 +75,7 @@ public class ZenModeSettingsTest { public void testGetBehaviorSettingSummary_alarmsAndMedia() { NotificationManager.Policy policy = new NotificationManager.Policy( NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS - | NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER, + | NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA, 0, 0); final String result = mBuilder.getBehaviorSettingSummary(policy, Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS); diff --git a/tests/robotests/src/com/android/settings/notification/ZenModeSystemPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/ZenModeSystemPreferenceControllerTest.java new file mode 100644 index 00000000000..46dfc6986c3 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/ZenModeSystemPreferenceControllerTest.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2018 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.notification; + +import static android.provider.Settings.Global.ZEN_MODE; +import static android.provider.Settings.Global.ZEN_MODE_ALARMS; +import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; +import static android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS; + +import static junit.framework.Assert.assertEquals; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.NotificationManager; +import android.content.ContentResolver; +import android.content.Context; +import android.provider.Settings; +import android.support.v14.preference.SwitchPreference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(SettingsRobolectricTestRunner.class) +public class ZenModeSystemPreferenceControllerTest { + private ZenModeSystemPreferenceController mController; + + @Mock + private ZenModeBackend mBackend; + @Mock + private NotificationManager mNotificationManager; + @Mock + private SwitchPreference mockPref; + @Mock + private NotificationManager.Policy mPolicy; + @Mock + private PreferenceScreen mPreferenceScreen; + + private Context mContext; + private ContentResolver mContentResolver; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNotificationManager); + + mContext = shadowApplication.getApplicationContext(); + mContentResolver = RuntimeEnvironment.application.getContentResolver(); + when(mNotificationManager.getNotificationPolicy()).thenReturn(mPolicy); + mController = new ZenModeSystemPreferenceController(mContext, mock(Lifecycle.class)); + ReflectionHelpers.setField(mController, "mBackend", mBackend); + + when(mPreferenceScreen.findPreference(mController.getPreferenceKey())).thenReturn( + mockPref); + mController.displayPreference(mPreferenceScreen); + } + + @Test + public void updateState_TotalSilence() { + Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_NO_INTERRUPTIONS); + + final SwitchPreference mockPref = mock(SwitchPreference.class); + mController.updateState(mockPref); + + verify(mockPref).setEnabled(false); + verify(mockPref).setChecked(false); + } + + @Test + public void updateState_AlarmsOnly() { + Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_ALARMS); + + final SwitchPreference mockPref = mock(SwitchPreference.class); + mController.updateState(mockPref); + + verify(mockPref).setEnabled(false); + verify(mockPref).setChecked(false); + } + + @Test + public void updateState_Priority() { + Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_IMPORTANT_INTERRUPTIONS); + + when(mBackend.isPriorityCategoryEnabled( + NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM)).thenReturn(true); + + mController.updateState(mockPref); + + verify(mockPref).setEnabled(true); + verify(mockPref).setChecked(true); + } + + @Test + public void onPreferenceChanged_EnableSystem() { + mController.onPreferenceChange(mockPref, true); + + verify(mBackend).saveSoundPolicy(NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM, + true); + } + + @Test + public void onPreferenceChanged_DisableSystem() { + mController.onPreferenceChange(mockPref, false); + + verify(mBackend).saveSoundPolicy(NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM, + false); + } +} \ No newline at end of file From 0ed66a4120a7b82a2619f386467de77749121b30 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Tue, 6 Mar 2018 14:56:14 -0800 Subject: [PATCH 02/17] Add dump log for BatteryTip. Since BatteryTipLoader will dump all BatteryTips, so we only need to add toString() for it. Bug: 74246970 Test: RunSettingsRoboTests Change-Id: I0a89c4ac06d107274d47f8b4b66867373c062c1b --- .../fuelgauge/batterytip/AppInfo.java | 6 ++++++ .../fuelgauge/batterytip/tips/BatteryTip.java | 5 +++++ .../batterytip/tips/HighUsageTip.java | 13 ++++++++++++ .../batterytip/tips/RestrictAppTip.java | 13 ++++++++++++ .../batterytip/tips/BatteryTipTest.java | 11 +++++++--- .../batterytip/tips/HighUsageTipTest.java | 6 ++++++ .../batterytip/tips/RestrictAppTipTest.java | 20 ++++++++++++------- 7 files changed, 64 insertions(+), 10 deletions(-) diff --git a/src/com/android/settings/fuelgauge/batterytip/AppInfo.java b/src/com/android/settings/fuelgauge/batterytip/AppInfo.java index 1daff36ab17..cf0376cb17a 100644 --- a/src/com/android/settings/fuelgauge/batterytip/AppInfo.java +++ b/src/com/android/settings/fuelgauge/batterytip/AppInfo.java @@ -64,6 +64,12 @@ public class AppInfo implements Comparable, Parcelable { dest.writeLong(screenOnTimeMs); } + @Override + public String toString() { + return "packageName=" + packageName + ",anomalyType=" + anomalyType + ",screenTime=" + + screenOnTimeMs; + } + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public AppInfo createFromParcel(Parcel in) { return new AppInfo(in); diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java index 3257d946539..f2526382f85 100644 --- a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java +++ b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java @@ -158,4 +158,9 @@ public abstract class BatteryTip implements Comparable, Parcelable { public int compareTo(BatteryTip o) { return TIP_ORDER.get(mType) - TIP_ORDER.get(o.mType); } + + @Override + public String toString() { + return "type=" + mType + " state=" + mState; + } } diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTip.java index 6091131d72d..475ea561e62 100644 --- a/src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTip.java +++ b/src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTip.java @@ -86,6 +86,19 @@ public class HighUsageTip extends BatteryTip { return mHighUsageAppList; } + @Override + public String toString() { + final StringBuilder stringBuilder = new StringBuilder(super.toString()); + stringBuilder.append(" {"); + for (int i = 0, size = mHighUsageAppList.size(); i < size; i++) { + final AppInfo appInfo = mHighUsageAppList.get(i); + stringBuilder.append(" " + appInfo.toString() + " "); + } + stringBuilder.append('}'); + + return stringBuilder.toString(); + } + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public BatteryTip createFromParcel(Parcel in) { return new HighUsageTip(in); diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTip.java index 4d286be5d55..037457a1f8b 100644 --- a/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTip.java +++ b/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTip.java @@ -97,6 +97,19 @@ public class RestrictAppTip extends BatteryTip { return mRestrictAppList; } + @Override + public String toString() { + final StringBuilder stringBuilder = new StringBuilder(super.toString()); + stringBuilder.append(" {"); + for (int i = 0, size = mRestrictAppList.size(); i < size; i++) { + final AppInfo appInfo = mRestrictAppList.get(i); + stringBuilder.append(" " + appInfo.toString() + " "); + } + stringBuilder.append('}'); + + return stringBuilder.toString(); + } + @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTipTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTipTest.java index 85c8ffc2ddb..753fc480be2 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTipTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTipTest.java @@ -52,7 +52,7 @@ public class BatteryTipTest { } @Test - public void testBuildPreference() { + public void buildPreference() { final Preference preference = mBatteryTip.buildPreference(mContext); assertThat(preference.getTitle()).isEqualTo(TITLE); @@ -61,7 +61,7 @@ public class BatteryTipTest { } @Test - public void testParcelable() { + public void parcelable() { final BatteryTip batteryTip = new TestBatteryTip(); Parcel parcel = Parcel.obtain(); @@ -76,7 +76,7 @@ public class BatteryTipTest { } @Test - public void testTipOrder_orderUnique() { + public void tipOrder_orderUnique() { final List orders = new ArrayList<>(); for (int i = 0, size = BatteryTip.TIP_ORDER.size(); i < size; i++) { orders.add(BatteryTip.TIP_ORDER.valueAt(i)); @@ -85,6 +85,11 @@ public class BatteryTipTest { assertThat(orders).containsNoDuplicates(); } + @Test + public void toString_containBatteryTipData() { + assertThat(mBatteryTip.toString()).isEqualTo("type=6 state=0"); + } + /** * Used to test the non abstract methods in {@link TestBatteryTip} */ diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTipTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTipTest.java index db2f37eb000..af32dc50335 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTipTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTipTest.java @@ -73,4 +73,10 @@ public class HighUsageTipTest { assertThat(app.packageName).isEqualTo(PACKAGE_NAME); assertThat(app.screenOnTimeMs).isEqualTo(SCREEN_TIME); } + + @Test + public void toString_containsAppData() { + assertThat(mBatteryTip.toString()).isEqualTo( + "type=2 state=0 { packageName=com.android.app,anomalyType=0,screenTime=1800000 }"); + } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTipTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTipTest.java index 20b896fd92c..3298ea84748 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTipTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTipTest.java @@ -71,7 +71,7 @@ public class RestrictAppTipTest { } @Test - public void testParcelable() { + public void parcelable() { Parcel parcel = Parcel.obtain(); mNewBatteryTip.writeToParcel(parcel, mNewBatteryTip.describeContents()); parcel.setDataPosition(0); @@ -85,40 +85,46 @@ public class RestrictAppTipTest { } @Test - public void testGetTitle_stateNew_showRestrictTitle() { + public void getTitle_stateNew_showRestrictTitle() { assertThat(mNewBatteryTip.getTitle(mContext)).isEqualTo("Restrict 1 app"); } @Test - public void testGetTitle_stateHandled_showHandledTitle() { + public void getTitle_stateHandled_showHandledTitle() { assertThat(mHandledBatteryTip.getTitle(mContext)).isEqualTo("1 recently restricted"); } @Test - public void testGetSummary_stateNew_showRestrictSummary() { + public void getSummary_stateNew_showRestrictSummary() { assertThat(mNewBatteryTip.getSummary(mContext)) .isEqualTo("app has high battery usage"); } @Test - public void testGetSummary_stateHandled_showHandledSummary() { + public void getSummary_stateHandled_showHandledSummary() { assertThat(mHandledBatteryTip.getSummary(mContext)) .isEqualTo("App changes are in progress"); } @Test - public void testUpdate_anomalyBecomeInvisible_stateHandled() { + public void update_anomalyBecomeInvisible_stateHandled() { mNewBatteryTip.updateState(mInvisibleBatteryTip); assertThat(mNewBatteryTip.getState()).isEqualTo(BatteryTip.StateType.HANDLED); } @Test - public void testUpdate_newAnomalyComes_stateNew() { + public void update_newAnomalyComes_stateNew() { mInvisibleBatteryTip.updateState(mNewBatteryTip); assertThat(mInvisibleBatteryTip.getState()).isEqualTo(BatteryTip.StateType.NEW); mHandledBatteryTip.updateState(mNewBatteryTip); assertThat(mHandledBatteryTip.getState()).isEqualTo(BatteryTip.StateType.NEW); } + + @Test + public void toString_containsAppData() { + assertThat(mNewBatteryTip.toString()).isEqualTo( + "type=1 state=0 { packageName=com.android.app,anomalyType=0,screenTime=0 }"); + } } From 36249c6254484831ad1db466618ca481df072dfb Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Wed, 28 Feb 2018 16:49:25 -0800 Subject: [PATCH 03/17] Update dabase manager to store the uid Also update the AppInfo to store the uid Bug: 74022362 Test: RunSettingsRoboTests Change-Id: I373242a12e9dbf48be134f2e9be30831b09f91c9 --- .../AnomalyDetectionJobService.java | 4 +-- .../fuelgauge/batterytip/AppInfo.java | 10 ++++++++ .../batterytip/BatteryDatabaseManager.java | 7 ++++-- .../fuelgauge/BatteryDatabaseManagerTest.java | 25 +++++++++++-------- .../fuelgauge/batterytip/AppInfoTest.java | 4 +++ 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobService.java b/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobService.java index 9d4f86f54c0..ff715253ad5 100644 --- a/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobService.java +++ b/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobService.java @@ -128,14 +128,14 @@ public class AnomalyDetectionJobService extends JobService { Log.e(TAG, "Excessive detected uid=" + uid); batteryUtils.setForceAppStandby(uid, packageName, AppOpsManager.MODE_IGNORED); - databaseManager.insertAnomaly(packageName, anomalyType, + databaseManager.insertAnomaly(uid, packageName, anomalyType, smartBatteryOn ? AnomalyDatabaseHelper.State.AUTO_HANDLED : AnomalyDatabaseHelper.State.NEW, timeMs); } } else { - databaseManager.insertAnomaly(packageName, anomalyType, + databaseManager.insertAnomaly(uid, packageName, anomalyType, AnomalyDatabaseHelper.State.NEW, timeMs); } } catch (NullPointerException | IndexOutOfBoundsException e) { diff --git a/src/com/android/settings/fuelgauge/batterytip/AppInfo.java b/src/com/android/settings/fuelgauge/batterytip/AppInfo.java index 1daff36ab17..00faa7c724f 100644 --- a/src/com/android/settings/fuelgauge/batterytip/AppInfo.java +++ b/src/com/android/settings/fuelgauge/batterytip/AppInfo.java @@ -33,11 +33,13 @@ public class AppInfo implements Comparable, Parcelable { */ public final int anomalyType; public final long screenOnTimeMs; + public final int uid; private AppInfo(AppInfo.Builder builder) { packageName = builder.mPackageName; anomalyType = builder.mAnomalyType; screenOnTimeMs = builder.mScreenOnTimeMs; + uid = builder.mUid; } @VisibleForTesting @@ -45,6 +47,7 @@ public class AppInfo implements Comparable, Parcelable { packageName = in.readString(); anomalyType = in.readInt(); screenOnTimeMs = in.readLong(); + uid = in.readInt(); } @Override @@ -62,6 +65,7 @@ public class AppInfo implements Comparable, Parcelable { dest.writeString(packageName); dest.writeInt(anomalyType); dest.writeLong(screenOnTimeMs); + dest.writeInt(uid); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @@ -78,6 +82,7 @@ public class AppInfo implements Comparable, Parcelable { private int mAnomalyType; private String mPackageName; private long mScreenOnTimeMs; + private int mUid; public Builder setAnomalyType(int type) { mAnomalyType = type; @@ -94,6 +99,11 @@ public class AppInfo implements Comparable, Parcelable { return this; } + public Builder setUid(int uid) { + mUid = uid; + return this; + } + public AppInfo build() { return new AppInfo(this); } diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryDatabaseManager.java b/src/com/android/settings/fuelgauge/batterytip/BatteryDatabaseManager.java index 935d4932575..d0bddecffad 100644 --- a/src/com/android/settings/fuelgauge/batterytip/BatteryDatabaseManager.java +++ b/src/com/android/settings/fuelgauge/batterytip/BatteryDatabaseManager.java @@ -24,6 +24,7 @@ import static com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper.An .ANOMALY_TYPE; import static com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper.AnomalyColumns .TIME_STAMP_MS; +import static com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper.AnomalyColumns.UID; import static com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper.Tables.TABLE_ANOMALY; import android.content.ContentValues; @@ -65,10 +66,11 @@ public class BatteryDatabaseManager { * @param anomalyState the state of the anomaly * @param timestampMs the time when it is happened */ - public synchronized void insertAnomaly(String packageName, int type, int anomalyState, + public synchronized void insertAnomaly(int uid, String packageName, int type, int anomalyState, long timestampMs) { try (SQLiteDatabase db = mDatabaseHelper.getWritableDatabase()) { ContentValues values = new ContentValues(); + values.put(UID, uid); values.put(PACKAGE_NAME, packageName); values.put(ANOMALY_TYPE, type); values.put(ANOMALY_STATE, anomalyState); @@ -83,7 +85,7 @@ public class BatteryDatabaseManager { public synchronized List queryAllAnomalies(long timestampMsAfter, int state) { final List appInfos = new ArrayList<>(); try (SQLiteDatabase db = mDatabaseHelper.getReadableDatabase()) { - final String[] projection = {PACKAGE_NAME, ANOMALY_TYPE}; + final String[] projection = {PACKAGE_NAME, ANOMALY_TYPE, UID}; final String orderBy = AnomalyDatabaseHelper.AnomalyColumns.TIME_STAMP_MS + " DESC"; try (Cursor cursor = db.query(TABLE_ANOMALY, projection, @@ -94,6 +96,7 @@ public class BatteryDatabaseManager { AppInfo appInfo = new AppInfo.Builder() .setPackageName(cursor.getString(cursor.getColumnIndex(PACKAGE_NAME))) .setAnomalyType(cursor.getInt(cursor.getColumnIndex(ANOMALY_TYPE))) + .setUid(cursor.getInt(cursor.getColumnIndex(UID))) .build(); appInfos.add(appInfo); } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryDatabaseManagerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryDatabaseManagerTest.java index 8b4ff5302b8..636023211bc 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryDatabaseManagerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryDatabaseManagerTest.java @@ -42,8 +42,10 @@ import java.util.List; public class BatteryDatabaseManagerTest { private static String PACKAGE_NAME_NEW = "com.android.app1"; + private static int UID_NEW = 345; private static int TYPE_NEW = 1; private static String PACKAGE_NAME_OLD = "com.android.app2"; + private static int UID_OLD = 543; private static int TYPE_OLD = 2; private static long NOW = System.currentTimeMillis(); private static long ONE_DAY_BEFORE = NOW - DateUtils.DAY_IN_MILLIS; @@ -67,23 +69,23 @@ public class BatteryDatabaseManagerTest { @Test public void testAllFunctions() { - mBatteryDatabaseManager.insertAnomaly(PACKAGE_NAME_NEW, TYPE_NEW, + mBatteryDatabaseManager.insertAnomaly(UID_NEW, PACKAGE_NAME_NEW, TYPE_NEW, AnomalyDatabaseHelper.State.NEW, NOW); - mBatteryDatabaseManager.insertAnomaly(PACKAGE_NAME_OLD, TYPE_OLD, + mBatteryDatabaseManager.insertAnomaly(UID_OLD, PACKAGE_NAME_OLD, TYPE_OLD, AnomalyDatabaseHelper.State.NEW, TWO_DAYS_BEFORE); // In database, it contains two record List totalAppInfos = mBatteryDatabaseManager.queryAllAnomalies(0 /* timeMsAfter */, AnomalyDatabaseHelper.State.NEW); assertThat(totalAppInfos).hasSize(2); - assertAppInfo(totalAppInfos.get(0), PACKAGE_NAME_NEW, TYPE_NEW); - assertAppInfo(totalAppInfos.get(1), PACKAGE_NAME_OLD, TYPE_OLD); + assertAppInfo(totalAppInfos.get(0), UID_NEW, PACKAGE_NAME_NEW, TYPE_NEW); + assertAppInfo(totalAppInfos.get(1), UID_OLD, PACKAGE_NAME_OLD, TYPE_OLD); // Only one record shows up if we query by timestamp List appInfos = mBatteryDatabaseManager.queryAllAnomalies(ONE_DAY_BEFORE, AnomalyDatabaseHelper.State.NEW); assertThat(appInfos).hasSize(1); - assertAppInfo(appInfos.get(0), PACKAGE_NAME_NEW, TYPE_NEW); + assertAppInfo(appInfos.get(0), UID_NEW, PACKAGE_NAME_NEW, TYPE_NEW); mBatteryDatabaseManager.deleteAllAnomaliesBeforeTimeStamp(ONE_DAY_BEFORE); @@ -91,14 +93,14 @@ public class BatteryDatabaseManagerTest { List appInfos1 = mBatteryDatabaseManager.queryAllAnomalies(0 /* timeMsAfter */, AnomalyDatabaseHelper.State.NEW); assertThat(appInfos1).hasSize(1); - assertAppInfo(appInfos1.get(0), PACKAGE_NAME_NEW, TYPE_NEW); + assertAppInfo(appInfos1.get(0), UID_NEW, PACKAGE_NAME_NEW, TYPE_NEW); } @Test public void testUpdateAnomalies_updateSuccessfully() { - mBatteryDatabaseManager.insertAnomaly(PACKAGE_NAME_NEW, TYPE_NEW, + mBatteryDatabaseManager.insertAnomaly(UID_NEW, PACKAGE_NAME_NEW, TYPE_NEW, AnomalyDatabaseHelper.State.NEW, NOW); - mBatteryDatabaseManager.insertAnomaly(PACKAGE_NAME_OLD, TYPE_OLD, + mBatteryDatabaseManager.insertAnomaly(UID_OLD, PACKAGE_NAME_OLD, TYPE_OLD, AnomalyDatabaseHelper.State.NEW, NOW); final AppInfo appInfo = new AppInfo.Builder().setPackageName(PACKAGE_NAME_OLD).build(); final List updateAppInfos = new ArrayList<>(); @@ -112,17 +114,18 @@ public class BatteryDatabaseManagerTest { List newAppInfos = mBatteryDatabaseManager.queryAllAnomalies(ONE_DAY_BEFORE, AnomalyDatabaseHelper.State.NEW); assertThat(newAppInfos).hasSize(1); - assertAppInfo(newAppInfos.get(0), PACKAGE_NAME_NEW, TYPE_NEW); + assertAppInfo(newAppInfos.get(0), UID_NEW, PACKAGE_NAME_NEW, TYPE_NEW); // The state of PACKAGE_NAME_OLD is changed to handled List handledAppInfos = mBatteryDatabaseManager.queryAllAnomalies(ONE_DAY_BEFORE, AnomalyDatabaseHelper.State.HANDLED); assertThat(handledAppInfos).hasSize(1); - assertAppInfo(handledAppInfos.get(0), PACKAGE_NAME_OLD, TYPE_OLD); + assertAppInfo(handledAppInfos.get(0), UID_OLD, PACKAGE_NAME_OLD, TYPE_OLD); } - private void assertAppInfo(final AppInfo appInfo, String packageName, int type) { + private void assertAppInfo(final AppInfo appInfo, int uid, String packageName, int type) { assertThat(appInfo.packageName).isEqualTo(packageName); assertThat(appInfo.anomalyType).isEqualTo(type); + assertThat(appInfo.uid).isEqualTo(uid); } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AppInfoTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AppInfoTest.java index 155351a1f2f..b140c4cb295 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AppInfoTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AppInfoTest.java @@ -38,6 +38,7 @@ public class AppInfoTest { private static final String PACKAGE_NAME = "com.android.app"; private static final int ANOMALY_TYPE = Anomaly.AnomalyType.WAKE_LOCK; private static final long SCREEN_TIME_MS = DateUtils.HOUR_IN_MILLIS; + private static final int UID = 3452; private AppInfo mAppInfo; @@ -47,6 +48,7 @@ public class AppInfoTest { .setPackageName(PACKAGE_NAME) .setAnomalyType(ANOMALY_TYPE) .setScreenOnTimeMs(SCREEN_TIME_MS) + .setUid(UID) .build(); } @@ -61,6 +63,7 @@ public class AppInfoTest { assertThat(appInfo.packageName).isEqualTo(PACKAGE_NAME); assertThat(appInfo.anomalyType).isEqualTo(ANOMALY_TYPE); assertThat(appInfo.screenOnTimeMs).isEqualTo(SCREEN_TIME_MS); + assertThat(appInfo.uid).isEqualTo(UID); } @Test @@ -84,5 +87,6 @@ public class AppInfoTest { assertThat(mAppInfo.packageName).isEqualTo(PACKAGE_NAME); assertThat(mAppInfo.anomalyType).isEqualTo(ANOMALY_TYPE); assertThat(mAppInfo.screenOnTimeMs).isEqualTo(SCREEN_TIME_MS); + assertThat(mAppInfo.uid).isEqualTo(UID); } } From eb562c9457c57c03f9aec336f0be3b455351ce43 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Wed, 28 Feb 2018 15:01:01 -0800 Subject: [PATCH 04/17] Make "Add device" as restricted preference. Restrict it by DISALLOW_CONFIG_BLUETOOTH Bug: 73739404 Test: Manual Change-Id: Ia550930ccf9f372b15826e24d3677124efd36fe9 --- res/xml/connected_devices.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/res/xml/connected_devices.xml b/res/xml/connected_devices.xml index 227dee25229..9b84a434d8e 100644 --- a/res/xml/connected_devices.xml +++ b/res/xml/connected_devices.xml @@ -28,13 +28,15 @@ android:key="saved_device_list" android:title="@string/connected_device_saved_title"/> - + settings:allowDividerAbove="true" + settings:userRestriction="no_config_bluetooth" + settings:useAdminDisabledSummary="true"/> Date: Tue, 6 Mar 2018 16:07:13 +0000 Subject: [PATCH 05/17] Catch SecurityException from enabling backup activity Cases where the activity is already enabled and the account is an unicorn account. Test: Opened activity from backup settings w/ unicorn account, verified no crash Bug: 74199770 Change-Id: I7dc30d22b186ff19cf7c40fda0363ff1288224d1 --- .../settings/backup/BackupSettingsActivity.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/com/android/settings/backup/BackupSettingsActivity.java b/src/com/android/settings/backup/BackupSettingsActivity.java index 7ffc6cd32f2..d78af32d36b 100644 --- a/src/com/android/settings/backup/BackupSettingsActivity.java +++ b/src/com/android/settings/backup/BackupSettingsActivity.java @@ -58,9 +58,17 @@ public class BackupSettingsActivity extends Activity implements Indexable { "No manufacturer settings found, launching the backup settings directly"); } Intent intent = backupHelper.getIntentForBackupSettings(); - // enable the activity before launching it - getPackageManager().setComponentEnabledSetting(intent.getComponent(), - PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); + try { + // enable the activity before launching it + getPackageManager().setComponentEnabledSetting( + intent.getComponent(), + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, + PackageManager.DONT_KILL_APP); + } catch (SecurityException e) { + Log.w(TAG, "Trying to enable activity " + intent.getComponent() + " but couldn't: " + + e.getMessage()); + // the activity may already be enabled + } // use startActivityForResult to let the activity check the caller signature startActivityForResult(intent, 1); From d5adf42c5e990382aa5bf76062d7008d2f49ab38 Mon Sep 17 00:00:00 2001 From: Victor Chang Date: Wed, 28 Feb 2018 19:37:36 +0000 Subject: [PATCH 06/17] New time zone picker page - Show current selected region and time zone in a 2 rows. Defailed info of current selected time zone in footer - Show option menu to switch to select UTC offset - This picker will be changed to the default picker in a later CL Bug: 73952488 Test: m RunSettingsRoboTests Change-Id: Ia81bb022e1021369612f5bd60c2c1f4d08db2af8 (cherry picked from commit b7d588f3415310c91313e4d0481f63edbfddbce3) --- res/xml/time_zone_prefs.xml | 42 ++ .../BaseTimeZonePreferenceController.java | 51 +++ .../FixedOffsetPreferenceController.java | 46 +++ .../timezone/OnPreferenceClickListener.java | 24 ++ .../timezone/RegionPreferenceController.java | 49 +++ .../RegionZonePreferenceController.java | 68 ++++ .../TimeZoneInfoPreferenceController.java | 137 +++++++ .../datetime/timezone/TimeZoneSettings.java | 372 ++++++++++++++++++ ...randfather_not_implementing_index_provider | 1 + .../BaseTimeZonePreferenceControllerTest.java | 99 +++++ .../FixedOffsetPreferenceControllerTest.java | 57 +++ .../RegionPreferenceControllerTest.java | 51 +++ .../RegionZonePreferenceControllerTest.java | 64 +++ .../TimeZoneInfoPreferenceControllerTest.java | 52 +++ .../timezone/TimeZoneSettingsTest.java | 69 ++++ 15 files changed, 1182 insertions(+) create mode 100644 res/xml/time_zone_prefs.xml create mode 100644 src/com/android/settings/datetime/timezone/BaseTimeZonePreferenceController.java create mode 100644 src/com/android/settings/datetime/timezone/FixedOffsetPreferenceController.java create mode 100644 src/com/android/settings/datetime/timezone/OnPreferenceClickListener.java create mode 100644 src/com/android/settings/datetime/timezone/RegionPreferenceController.java create mode 100644 src/com/android/settings/datetime/timezone/RegionZonePreferenceController.java create mode 100644 src/com/android/settings/datetime/timezone/TimeZoneInfoPreferenceController.java create mode 100644 src/com/android/settings/datetime/timezone/TimeZoneSettings.java create mode 100644 tests/robotests/src/com/android/settings/datetime/timezone/BaseTimeZonePreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/datetime/timezone/FixedOffsetPreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/datetime/timezone/RegionPreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/datetime/timezone/RegionZonePreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/datetime/timezone/TimeZoneInfoPreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/datetime/timezone/TimeZoneSettingsTest.java diff --git a/res/xml/time_zone_prefs.xml b/res/xml/time_zone_prefs.xml new file mode 100644 index 00000000000..f80de8c1e5c --- /dev/null +++ b/res/xml/time_zone_prefs.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + diff --git a/src/com/android/settings/datetime/timezone/BaseTimeZonePreferenceController.java b/src/com/android/settings/datetime/timezone/BaseTimeZonePreferenceController.java new file mode 100644 index 00000000000..846fce02ffc --- /dev/null +++ b/src/com/android/settings/datetime/timezone/BaseTimeZonePreferenceController.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2018 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.datetime.timezone; + +import android.content.Context; +import android.support.v7.preference.Preference; + +import com.android.settings.core.BasePreferenceController; + +import com.google.common.base.Objects; + +public abstract class BaseTimeZonePreferenceController extends BasePreferenceController { + private OnPreferenceClickListener mOnClickListener; + + protected BaseTimeZonePreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (mOnClickListener == null || !Objects.equal(getPreferenceKey(), preference.getKey())) { + return false; + } + + mOnClickListener.onClick(); + return true; + } + + public void setOnClickListener(OnPreferenceClickListener listener) { + mOnClickListener = listener; + } +} diff --git a/src/com/android/settings/datetime/timezone/FixedOffsetPreferenceController.java b/src/com/android/settings/datetime/timezone/FixedOffsetPreferenceController.java new file mode 100644 index 00000000000..16c1f193e5a --- /dev/null +++ b/src/com/android/settings/datetime/timezone/FixedOffsetPreferenceController.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018 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.datetime.timezone; + +import android.content.Context; +import android.support.v7.preference.Preference; + +public class FixedOffsetPreferenceController extends BaseTimeZonePreferenceController { + + private static final String PREFERENCE_KEY = "fixed_offset"; + + private TimeZoneInfo mTimeZoneInfo; + + public FixedOffsetPreferenceController(Context context) { + super(context, PREFERENCE_KEY); + } + + @Override + public CharSequence getSummary() { + // This is a Spannable object, which contains TTS span. It shouldn't be converted to String. + return mTimeZoneInfo == null ? "" : mTimeZoneInfo.getGmtOffset(); + } + + public void setTimeZoneInfo(TimeZoneInfo timeZoneInfo) { + mTimeZoneInfo = timeZoneInfo; + } + + public TimeZoneInfo getTimeZoneInfo() { + return mTimeZoneInfo; + } +} + diff --git a/src/com/android/settings/datetime/timezone/OnPreferenceClickListener.java b/src/com/android/settings/datetime/timezone/OnPreferenceClickListener.java new file mode 100644 index 00000000000..3e4d7152908 --- /dev/null +++ b/src/com/android/settings/datetime/timezone/OnPreferenceClickListener.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2018 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.datetime.timezone; + +/** + * Callback when a preference is clicked in {@class TimeZoneSettings} + */ +public interface OnPreferenceClickListener { + void onClick(); +} diff --git a/src/com/android/settings/datetime/timezone/RegionPreferenceController.java b/src/com/android/settings/datetime/timezone/RegionPreferenceController.java new file mode 100644 index 00000000000..201b9bd2e22 --- /dev/null +++ b/src/com/android/settings/datetime/timezone/RegionPreferenceController.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2018 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.datetime.timezone; + +import android.content.Context; +import android.icu.text.LocaleDisplayNames; +import android.support.v7.preference.Preference; + +import java.util.Locale; + +public class RegionPreferenceController extends BaseTimeZonePreferenceController { + private static final String PREFERENCE_KEY = "region"; + + private final LocaleDisplayNames mLocaleDisplayNames; + private String mRegionId = ""; + + public RegionPreferenceController(Context context) { + super(context, PREFERENCE_KEY); + Locale locale = context.getResources().getConfiguration().getLocales().get(0); + mLocaleDisplayNames = LocaleDisplayNames.getInstance(locale); + + } + + @Override + public CharSequence getSummary() { + return mLocaleDisplayNames.regionDisplayName(mRegionId); + } + + public void setRegionId(String regionId) { + mRegionId = regionId; + } + + public String getRegionId() { + return mRegionId; + } +} diff --git a/src/com/android/settings/datetime/timezone/RegionZonePreferenceController.java b/src/com/android/settings/datetime/timezone/RegionZonePreferenceController.java new file mode 100644 index 00000000000..85f41658fe4 --- /dev/null +++ b/src/com/android/settings/datetime/timezone/RegionZonePreferenceController.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2018 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.datetime.timezone; + +import android.content.Context; +import android.support.v7.preference.Preference; + +import com.android.settings.R; + +public class RegionZonePreferenceController extends BaseTimeZonePreferenceController { + private static final String PREFERENCE_KEY = "region_zone"; + + private TimeZoneInfo mTimeZoneInfo; + private boolean mIsClickable; + + public RegionZonePreferenceController(Context context) { + super(context, PREFERENCE_KEY); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + preference.setEnabled(isClickable()); + } + + @Override + public CharSequence getSummary() { + return mTimeZoneInfo == null ? "" + : SpannableUtil.getResourcesText(mContext.getResources(), + R.string.zone_info_exemplar_location_and_offset, + mTimeZoneInfo.getExemplarLocation(), mTimeZoneInfo.getGmtOffset()); + } + + public void setTimeZoneInfo(TimeZoneInfo timeZoneInfo) { + mTimeZoneInfo = timeZoneInfo; + } + + public TimeZoneInfo getTimeZoneInfo() { + return mTimeZoneInfo; + } + + public void setClickable(boolean clickable) { + mIsClickable = clickable; + } + + public boolean isClickable() { + return mIsClickable; + } +} diff --git a/src/com/android/settings/datetime/timezone/TimeZoneInfoPreferenceController.java b/src/com/android/settings/datetime/timezone/TimeZoneInfoPreferenceController.java new file mode 100644 index 00000000000..0f0264f516c --- /dev/null +++ b/src/com/android/settings/datetime/timezone/TimeZoneInfoPreferenceController.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2018 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.datetime.timezone; + +import android.content.Context; +import android.icu.impl.OlsonTimeZone; +import android.icu.text.DateFormat; +import android.icu.text.DisplayContext; +import android.icu.text.SimpleDateFormat; +import android.icu.util.Calendar; +import android.icu.util.TimeZone; +import android.icu.util.TimeZoneTransition; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; + +import com.android.settings.R; +import com.android.settingslib.widget.FooterPreference; + +import java.util.Date; + +public class TimeZoneInfoPreferenceController extends BaseTimeZonePreferenceController { + private static final String PREFERENCE_KEY = FooterPreference.KEY_FOOTER; + + private TimeZoneInfo mTimeZoneInfo; + private final DateFormat mDateFormat; + private final Date mDate; + + public TimeZoneInfoPreferenceController(Context context) { + this(context, new Date()); + } + + @VisibleForTesting + TimeZoneInfoPreferenceController(Context context, Date date) { + super(context, PREFERENCE_KEY); + mDateFormat = DateFormat.getDateInstance(SimpleDateFormat.LONG); + mDateFormat.setContext(DisplayContext.CAPITALIZATION_NONE); + mDate = date; + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public void updateState(Preference preference) { + CharSequence formattedTimeZone = mTimeZoneInfo == null ? "" : formatInfo(mTimeZoneInfo); + preference.setTitle(formattedTimeZone); + preference.setVisible(mTimeZoneInfo != null); + } + + public void setTimeZoneInfo(TimeZoneInfo timeZoneInfo) { + mTimeZoneInfo = timeZoneInfo; + } + + public TimeZoneInfo getTimeZoneInfo() { + return mTimeZoneInfo; + } + + private CharSequence formatOffsetAndName(TimeZoneInfo item) { + String name = item.getGenericName(); + if (name == null) { + if (item.getTimeZone().inDaylightTime(mDate)) { + name = item.getDaylightName(); + } else { + name = item.getStandardName(); + } + } + if (name == null) { + return item.getGmtOffset().toString(); + } else { + return SpannableUtil.getResourcesText(mContext.getResources(), + R.string.zone_info_offset_and_name, item.getGmtOffset(), + name); + } + } + + private CharSequence formatInfo(TimeZoneInfo item) { + final CharSequence offsetAndName = formatOffsetAndName(item); + final TimeZone timeZone = item.getTimeZone(); + if (!timeZone.observesDaylightTime()) { + return mContext.getString(R.string.zone_info_footer_no_dst, offsetAndName); + } + + final TimeZoneTransition nextDstTransition = findNextDstTransition(timeZone); + if (nextDstTransition == null) { + return null; + } + final boolean toDst = nextDstTransition.getTo().getDSTSavings() != 0; + String timeType = toDst ? item.getDaylightName() : item.getStandardName(); + if (timeType == null) { + // Fall back to generic "summer time" and "standard time" if the time zone has no + // specific names. + timeType = toDst ? + mContext.getString(R.string.zone_time_type_dst) : + mContext.getString(R.string.zone_time_type_standard); + + } + final Calendar transitionTime = Calendar.getInstance(timeZone); + transitionTime.setTimeInMillis(nextDstTransition.getTime()); + final String date = mDateFormat.format(transitionTime); + return SpannableUtil.getResourcesText(mContext.getResources(), + R.string.zone_info_footer, offsetAndName, timeType, date); + } + + private TimeZoneTransition findNextDstTransition(TimeZone timeZone) { + if (!(timeZone instanceof OlsonTimeZone)) { + return null; + } + final OlsonTimeZone olsonTimeZone = (OlsonTimeZone) timeZone; + TimeZoneTransition transition = olsonTimeZone.getNextTransition( + mDate.getTime(), /* inclusive */ false); + do { + if (transition.getTo().getDSTSavings() != transition.getFrom().getDSTSavings()) { + break; + } + transition = olsonTimeZone.getNextTransition( + transition.getTime(), /*inclusive */ false); + } while (transition != null); + return transition; + } + +} diff --git a/src/com/android/settings/datetime/timezone/TimeZoneSettings.java b/src/com/android/settings/datetime/timezone/TimeZoneSettings.java new file mode 100644 index 00000000000..aeb5a8003bb --- /dev/null +++ b/src/com/android/settings/datetime/timezone/TimeZoneSettings.java @@ -0,0 +1,372 @@ +/* + * Copyright (C) 2018 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.datetime.timezone; + +import android.app.Activity; +import android.app.AlarmManager; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.icu.util.TimeZone; +import android.os.Bundle; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.PreferenceCategory; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.R; +import com.android.settings.core.SubSettingLauncher; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.datetime.timezone.model.FilteredCountryTimeZones; +import com.android.settings.datetime.timezone.model.TimeZoneData; +import com.android.settings.datetime.timezone.model.TimeZoneDataLoader; +import com.android.settingslib.core.AbstractPreferenceController; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.Set; + +/** + * The class displays a time zone picker either by regions or fixed offset time zones. + */ +public class TimeZoneSettings extends DashboardFragment { + + private static final String TAG = "TimeZoneSettings"; + + private static final int MENU_BY_REGION = Menu.FIRST; + private static final int MENU_BY_OFFSET = Menu.FIRST + 1; + + private static final int REQUEST_CODE_REGION_PICKER = 1; + private static final int REQUEST_CODE_ZONE_PICKER = 2; + private static final int REQUEST_CODE_FIXED_OFFSET_ZONE_PICKER = 3; + + private static final String PREF_KEY_REGION = "time_zone_region"; + private static final String PREF_KEY_REGION_CATEGORY = "time_zone_region_preference_category"; + private static final String PREF_KEY_FIXED_OFFSET_CATEGORY = + "time_zone_fixed_offset_preference_category"; + + private Locale mLocale; + private boolean mSelectByRegion; + private TimeZoneData mTimeZoneData; + + private String mSelectedTimeZoneId; + private TimeZoneInfo.Formatter mTimeZoneInfoFormatter; + + @Override + public int getMetricsCategory() { + return MetricsProto.MetricsEvent.ZONE_PICKER; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.time_zone_prefs; + } + + @Override + protected String getLogTag() { + return TAG; + } + + /** + * Called during onAttach + */ + @VisibleForTesting + @Override + public List createPreferenceControllers(Context context) { + mLocale = context.getResources().getConfiguration().getLocales().get(0); + mTimeZoneInfoFormatter = new TimeZoneInfo.Formatter(mLocale, new Date()); + final List controllers = new ArrayList<>(); + RegionPreferenceController regionPreferenceController = + new RegionPreferenceController(context); + regionPreferenceController.setOnClickListener(this::onRegionPreferenceClicked); + RegionZonePreferenceController regionZonePreferenceController = + new RegionZonePreferenceController(context); + regionZonePreferenceController.setOnClickListener(this::onRegionZonePreferenceClicked); + TimeZoneInfoPreferenceController timeZoneInfoPreferenceController = + new TimeZoneInfoPreferenceController(context); + FixedOffsetPreferenceController fixedOffsetPreferenceController = + new FixedOffsetPreferenceController(context); + fixedOffsetPreferenceController.setOnClickListener(this::onFixedOffsetPreferenceClicked); + + controllers.add(regionPreferenceController); + controllers.add(regionZonePreferenceController); + controllers.add(timeZoneInfoPreferenceController); + controllers.add(fixedOffsetPreferenceController); + return controllers; + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + // Hide all interactive preferences + setPreferenceCategoryVisible((PreferenceCategory) findPreference( + PREF_KEY_REGION_CATEGORY), false); + setPreferenceCategoryVisible((PreferenceCategory) findPreference( + PREF_KEY_FIXED_OFFSET_CATEGORY), false); + + // Start loading TimeZoneData + getLoaderManager().initLoader(0, null, new TimeZoneDataLoader.LoaderCreator( + getContext(), this::onTimeZoneDataReady)); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode != Activity.RESULT_OK || data == null) { + return; + } + + switch (requestCode) { + case REQUEST_CODE_REGION_PICKER: + case REQUEST_CODE_ZONE_PICKER: { + String regionId = data.getStringExtra(RegionSearchPicker.EXTRA_RESULT_REGION_ID); + String tzId = data.getStringExtra(RegionZonePicker.EXTRA_RESULT_TIME_ZONE_ID); + // Ignore the result if user didn't change the region or time zone. + if (!Objects.equals(regionId, use(RegionPreferenceController.class).getRegionId()) + || !Objects.equals(tzId, mSelectedTimeZoneId)) { + onRegionZoneChanged(regionId, tzId); + } + break; + } + case REQUEST_CODE_FIXED_OFFSET_ZONE_PICKER: { + String tzId = data.getStringExtra(FixedOffsetPicker.EXTRA_RESULT_TIME_ZONE_ID); + // Ignore the result if user didn't change the time zone. + if (tzId != null && !tzId.equals(mSelectedTimeZoneId)) { + onFixedOffsetZoneChanged(tzId); + } + break; + } + } + } + + @VisibleForTesting + void setTimeZoneData(TimeZoneData timeZoneData) { + mTimeZoneData = timeZoneData; + } + + private void onTimeZoneDataReady(TimeZoneData timeZoneData) { + if (mTimeZoneData == null && timeZoneData != null) { + mTimeZoneData = timeZoneData; + setupForCurrentTimeZone(); + getActivity().invalidateOptionsMenu(); + } + + } + + private void onRegionPreferenceClicked() { + startPickerFragment(RegionSearchPicker.class, new Bundle(), REQUEST_CODE_REGION_PICKER); + } + + private void onRegionZonePreferenceClicked() { + final Bundle args = new Bundle(); + args.putString(RegionZonePicker.EXTRA_REGION_ID, + use(RegionPreferenceController.class).getRegionId()); + startPickerFragment(RegionZonePicker.class, args, REQUEST_CODE_ZONE_PICKER); + } + + private void onFixedOffsetPreferenceClicked() { + startPickerFragment(FixedOffsetPicker.class, new Bundle(), + REQUEST_CODE_FIXED_OFFSET_ZONE_PICKER); + } + + private void startPickerFragment(Class fragmentClass, Bundle args, + int resultRequestCode) { + new SubSettingLauncher(getContext()) + .setDestination(fragmentClass.getCanonicalName()) + .setArguments(args) + .setSourceMetricsCategory(getMetricsCategory()) + .setResultListener(this, resultRequestCode) + .launch(); + } + + private void setDisplayedRegion(String regionId) { + use(RegionPreferenceController.class).setRegionId(regionId); + updatePreferenceStates(); + } + + private void setDisplayedTimeZoneInfo(String regionId, String tzId) { + final TimeZoneInfo tzInfo = tzId == null ? null : mTimeZoneInfoFormatter.format(tzId); + final FilteredCountryTimeZones countryTimeZones = + mTimeZoneData.lookupCountryTimeZones(regionId); + + use(RegionZonePreferenceController.class).setTimeZoneInfo(tzInfo); + // Only clickable when the region has more than 1 time zones or no time zone is selected. + + use(RegionZonePreferenceController.class).setClickable(tzInfo == null || + (countryTimeZones != null && countryTimeZones.getTimeZoneIds().size() > 1)); + use(TimeZoneInfoPreferenceController.class).setTimeZoneInfo(tzInfo); + + updatePreferenceStates(); + } + + private void setDisplayedFixedOffsetTimeZoneInfo(String tzId) { + if (isFixedOffset(tzId)) { + use(FixedOffsetPreferenceController.class).setTimeZoneInfo( + mTimeZoneInfoFormatter.format(tzId)); + } else { + use(FixedOffsetPreferenceController.class).setTimeZoneInfo(null); + } + updatePreferenceStates(); + } + + private void onRegionZoneChanged(String regionId, String tzId) { + FilteredCountryTimeZones countryTimeZones = + mTimeZoneData.lookupCountryTimeZones(regionId); + if (countryTimeZones == null || !countryTimeZones.getTimeZoneIds().contains(tzId)) { + Log.e(TAG, "Unknown time zone id is selected: " + tzId); + return; + } + + mSelectedTimeZoneId = tzId; + setDisplayedRegion(regionId); + setDisplayedTimeZoneInfo(regionId, mSelectedTimeZoneId); + saveTimeZone(regionId, mSelectedTimeZoneId); + } + + private void onFixedOffsetZoneChanged(String tzId) { + mSelectedTimeZoneId = tzId; + setDisplayedFixedOffsetTimeZoneInfo(tzId); + saveTimeZone(null, mSelectedTimeZoneId); + } + + private void saveTimeZone(String regionId, String tzId) { + SharedPreferences.Editor editor = getPreferenceManager().getSharedPreferences().edit(); + if (regionId == null) { + editor.remove(PREF_KEY_REGION); + } else { + editor.putString(PREF_KEY_REGION, regionId); + } + editor.apply(); + getActivity().getSystemService(AlarmManager.class).setTimeZone(tzId); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + menu.add(0, MENU_BY_REGION, 0, R.string.zone_menu_by_region); + menu.add(0, MENU_BY_OFFSET, 0, R.string.zone_menu_by_offset); + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public void onPrepareOptionsMenu(Menu menu) { + // Do not show menu when data is not ready, + menu.findItem(MENU_BY_REGION).setVisible(mTimeZoneData != null && !mSelectByRegion); + menu.findItem(MENU_BY_OFFSET).setVisible(mTimeZoneData != null && mSelectByRegion); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case MENU_BY_REGION: + setSelectByRegion(true); + return true; + + case MENU_BY_OFFSET: + setSelectByRegion(false); + return true; + + default: + return false; + } + } + + private void setupForCurrentTimeZone() { + mSelectedTimeZoneId = TimeZone.getDefault().getID(); + setSelectByRegion(!isFixedOffset(mSelectedTimeZoneId)); + } + + private static boolean isFixedOffset(String tzId) { + return tzId.startsWith("Etc/GMT") || tzId.equals("Etc/UTC"); + } + + /** + * Switch the current view to select region or select fixed offset time zone. + * When showing the selected region, it guess the selected region from time zone id. + * See {@link #findRegionIdForTzId} for more info. + */ + private void setSelectByRegion(boolean selectByRegion) { + mSelectByRegion = selectByRegion; + setPreferenceCategoryVisible((PreferenceCategory) findPreference( + PREF_KEY_REGION_CATEGORY), selectByRegion); + setPreferenceCategoryVisible((PreferenceCategory) findPreference( + PREF_KEY_FIXED_OFFSET_CATEGORY), !selectByRegion); + final String localeRegionId = getLocaleRegionId(); + final Set allCountryIsoCodes = mTimeZoneData.getRegionIds(); + + String displayRegion = allCountryIsoCodes.contains(localeRegionId) ? localeRegionId : null; + setDisplayedRegion(displayRegion); + setDisplayedTimeZoneInfo(displayRegion, null); + + if (!mSelectByRegion) { + setDisplayedFixedOffsetTimeZoneInfo(mSelectedTimeZoneId); + return; + } + + String regionId = findRegionIdForTzId(mSelectedTimeZoneId); + if (regionId != null) { + setDisplayedRegion(regionId); + setDisplayedTimeZoneInfo(regionId, mSelectedTimeZoneId); + } + } + + /** + * Find the a region associated with the specified time zone, based on the time zone data. + * If there are multiple regions associated with the given time zone, the priority will be given + * to the region the user last picked and the country in user's locale. + * @return null if no region associated with the time zone + */ + private String findRegionIdForTzId(String tzId) { + return findRegionIdForTzId(tzId, + getPreferenceManager().getSharedPreferences().getString(PREF_KEY_REGION, null), + getLocaleRegionId()); + } + + @VisibleForTesting + String findRegionIdForTzId(String tzId, String sharePrefRegionId, String localeRegionId) { + final Set matchedRegions = mTimeZoneData.lookupCountryCodesForZoneId(tzId); + if (matchedRegions.size() == 0) { + return null; + } + if (sharePrefRegionId != null && matchedRegions.contains(sharePrefRegionId)) { + return sharePrefRegionId; + } + if (localeRegionId != null && matchedRegions.contains(localeRegionId)) { + return localeRegionId; + } + + return matchedRegions.toArray(new String[matchedRegions.size()])[0]; + } + + private void setPreferenceCategoryVisible(PreferenceCategory category, + boolean isVisible) { + // Hiding category doesn't hide all the children preference. Set visibility of its children. + // Do not care grandchildren as time_zone_pref.xml has only 2 levels. + category.setVisible(isVisible); + for (int i = 0; i < category.getPreferenceCount(); i++) { + category.getPreference(i).setVisible(isVisible); + } + } + + private String getLocaleRegionId() { + return mLocale.getCountry().toUpperCase(Locale.US); + } +} diff --git a/tests/robotests/assets/grandfather_not_implementing_index_provider b/tests/robotests/assets/grandfather_not_implementing_index_provider index 43697bdd529..223d8f8d6df 100644 --- a/tests/robotests/assets/grandfather_not_implementing_index_provider +++ b/tests/robotests/assets/grandfather_not_implementing_index_provider @@ -24,3 +24,4 @@ com.android.settings.wifi.SavedAccessPointsWifiSettings com.android.settings.notification.ZenModeEventRuleSettings com.android.settings.notification.ZenModeScheduleRuleSettings com.android.settings.fuelgauge.RestrictedAppDetails +com.android.settings.datetime.timezone.TimeZoneSettings diff --git a/tests/robotests/src/com/android/settings/datetime/timezone/BaseTimeZonePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/datetime/timezone/BaseTimeZonePreferenceControllerTest.java new file mode 100644 index 00000000000..49c468edef0 --- /dev/null +++ b/tests/robotests/src/com/android/settings/datetime/timezone/BaseTimeZonePreferenceControllerTest.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2018 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.datetime.timezone; + +import android.app.Activity; +import android.content.Context; +import android.support.v7.preference.Preference; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RuntimeEnvironment; + +import static com.google.common.truth.Truth.assertThat; + +@RunWith(SettingsRobolectricTestRunner.class) +public class BaseTimeZonePreferenceControllerTest { + + private Activity mActivity; + + @Before + public void setUp() { + mActivity = Robolectric.setupActivity(Activity.class); + } + + @Test + public void handlePreferenceTreeClick_correctKey_triggerOnClickListener() { + String prefKey = "key1"; + TestClickListener clickListener = new TestClickListener(); + TestPreference preference = new TestPreference(mActivity, prefKey); + TestPreferenceController controller = new TestPreferenceController(mActivity, prefKey); + controller.setOnClickListener(clickListener); + + controller.handlePreferenceTreeClick(preference); + assertThat(clickListener.isClicked()).isTrue(); + } + + @Test + public void handlePreferenceTreeClick_wrongKey_triggerOnClickListener() { + String prefKey = "key1"; + TestClickListener clickListener = new TestClickListener(); + TestPreference preference = new TestPreference(mActivity, "wrong_key"); + TestPreferenceController controller = new TestPreferenceController(mActivity, prefKey); + controller.setOnClickListener(clickListener); + + controller.handlePreferenceTreeClick(preference); + assertThat(clickListener.isClicked()).isFalse(); + } + + private static class TestPreferenceController extends BaseTimeZonePreferenceController { + + private final Preference mTestPreference; + + public TestPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + mTestPreference = new Preference(context); + mTestPreference.setKey(preferenceKey); + } + } + + private static class TestPreference extends Preference { + public TestPreference(Context context, String preferenceKey) { + super(context); + setKey(preferenceKey); + } + } + + private static class TestClickListener implements OnPreferenceClickListener { + + private boolean isClicked = false; + + @Override + public void onClick() { + isClicked = true; + } + + public boolean isClicked() { + return isClicked; + } + + } +} diff --git a/tests/robotests/src/com/android/settings/datetime/timezone/FixedOffsetPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/datetime/timezone/FixedOffsetPreferenceControllerTest.java new file mode 100644 index 00000000000..0ffb7d2c45d --- /dev/null +++ b/tests/robotests/src/com/android/settings/datetime/timezone/FixedOffsetPreferenceControllerTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2018 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.datetime.timezone; + +import android.app.Activity; +import android.icu.util.TimeZone; +import android.support.v7.preference.Preference; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; + +import static com.google.common.truth.Truth.assertThat; + +@RunWith(SettingsRobolectricTestRunner.class) +public class FixedOffsetPreferenceControllerTest { + + private Activity mActivity; + + @Before + public void setUp() { + mActivity = Robolectric.setupActivity(Activity.class); + } + + @Test + public void updateState_matchTimeZoneSummary() { + TimeZoneInfo fixedOffsetZone = new TimeZoneInfo.Builder( + TimeZone.getFrozenTimeZone("Etc/GMT-8")) + .setExemplarLocation("Los Angeles") + .setGmtOffset("GMT-08:00") + .setItemId(0) + .build(); + Preference preference = new Preference(mActivity); + FixedOffsetPreferenceController controller = new FixedOffsetPreferenceController(mActivity); + controller.setTimeZoneInfo(fixedOffsetZone); + controller.updateState(preference); + assertThat(preference.getSummary()).isEqualTo("GMT-08:00"); + + } +} diff --git a/tests/robotests/src/com/android/settings/datetime/timezone/RegionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/datetime/timezone/RegionPreferenceControllerTest.java new file mode 100644 index 00000000000..7a8f267c178 --- /dev/null +++ b/tests/robotests/src/com/android/settings/datetime/timezone/RegionPreferenceControllerTest.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2018 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.datetime.timezone; + +import android.app.Activity; +import android.support.v7.preference.Preference; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; + +import static com.google.common.truth.Truth.assertThat; + +@RunWith(SettingsRobolectricTestRunner.class) +public class RegionPreferenceControllerTest { + + private Activity mActivity; + + @Before + public void setUp() { + mActivity = Robolectric.setupActivity(Activity.class); + } + + @Test + public void updateState_matchCountryName() { + Preference preference = new Preference(mActivity); + RegionPreferenceController controller = new RegionPreferenceController(mActivity); + controller.setRegionId("US"); + controller.updateState(preference); + assertThat(controller.getSummary()).isEqualTo("United States"); + assertThat(preference.getSummary()).isEqualTo("United States"); + + } +} diff --git a/tests/robotests/src/com/android/settings/datetime/timezone/RegionZonePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/datetime/timezone/RegionZonePreferenceControllerTest.java new file mode 100644 index 00000000000..b39641fc1a7 --- /dev/null +++ b/tests/robotests/src/com/android/settings/datetime/timezone/RegionZonePreferenceControllerTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2018 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.datetime.timezone; + +import android.app.Activity; +import android.icu.util.TimeZone; +import android.support.v7.preference.Preference; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; + +import static com.google.common.truth.Truth.assertThat; + +@RunWith(SettingsRobolectricTestRunner.class) +public class RegionZonePreferenceControllerTest { + + private Activity mActivity; + + @Before + public void setUp() { + mActivity = Robolectric.setupActivity(Activity.class); + } + + @Test + public void updateState_matchTimeZoneName() { + TimeZoneInfo tzInfo = new TimeZoneInfo.Builder( + TimeZone.getFrozenTimeZone("America/Los_Angeles")) + .setGenericName("Pacific Time") + .setStandardName("Pacific Standard Time") + .setDaylightName("Pacific Daylight Time") + .setExemplarLocation("Los Angeles") + .setGmtOffset("GMT-08:00") + .setItemId(0) + .build(); + Preference preference = new Preference(mActivity); + RegionZonePreferenceController controller = new RegionZonePreferenceController(mActivity); + controller.setTimeZoneInfo(tzInfo); + controller.setClickable(false); + controller.updateState(preference); + String expectedSummary = "Los Angeles (GMT-08:00)"; + assertThat(controller.getSummary().toString()).isEqualTo(expectedSummary); + assertThat(preference.getSummary().toString()).isEqualTo(expectedSummary); + assertThat(preference.isEnabled()).isFalse(); + + } +} diff --git a/tests/robotests/src/com/android/settings/datetime/timezone/TimeZoneInfoPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/datetime/timezone/TimeZoneInfoPreferenceControllerTest.java new file mode 100644 index 00000000000..2a587704e42 --- /dev/null +++ b/tests/robotests/src/com/android/settings/datetime/timezone/TimeZoneInfoPreferenceControllerTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2018 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.datetime.timezone; + +import android.support.v7.preference.Preference; + +import com.android.settings.datetime.timezone.TimeZoneInfo.Formatter; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; + +import java.util.Date; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.spy; + +@RunWith(SettingsRobolectricTestRunner.class) +public class TimeZoneInfoPreferenceControllerTest { + + @Test + public void updateState_matchExpectedFormattedText() { + Date now = new Date(0L); // 00:00 1/1/1970 + Formatter formatter = new Formatter(Locale.US, now); + + TimeZoneInfo timeZoneInfo = formatter.format("America/Los_Angeles"); + TimeZoneInfoPreferenceController controller = + new TimeZoneInfoPreferenceController(RuntimeEnvironment.application, now); + controller.setTimeZoneInfo(timeZoneInfo); + Preference preference = spy(new Preference(RuntimeEnvironment.application)); + controller.updateState(preference); + assertEquals("Uses Pacific Time (GMT-08:00). " + + "Pacific Daylight Time starts on April 26, 1970.", + preference.getTitle().toString()); + } +} diff --git a/tests/robotests/src/com/android/settings/datetime/timezone/TimeZoneSettingsTest.java b/tests/robotests/src/com/android/settings/datetime/timezone/TimeZoneSettingsTest.java new file mode 100644 index 00000000000..21ca30d7464 --- /dev/null +++ b/tests/robotests/src/com/android/settings/datetime/timezone/TimeZoneSettingsTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2018 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.datetime.timezone; + +import com.android.settings.datetime.timezone.model.TimeZoneData; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.SettingsShadowResources; +import com.android.settingslib.core.AbstractPreferenceController; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(shadows = { + SettingsShadowResources.class, + SettingsShadowResources.SettingsShadowTheme.class, + }) +public class TimeZoneSettingsTest { + + @Test + public void findRegionIdForTzId_matchExpectedCountry() { + String tzId = "Unknown/Secret_City"; + TimeZoneData timeZoneData = mock(TimeZoneData.class); + when(timeZoneData.lookupCountryCodesForZoneId(tzId)) + .thenReturn(new HashSet<>(Arrays.asList("US", "GB"))); + + TimeZoneSettings settings = new TimeZoneSettings(); + settings.setTimeZoneData(timeZoneData); + assertThat(settings.findRegionIdForTzId(tzId, null, "")).matches("US|GB"); + assertThat(settings.findRegionIdForTzId(tzId, "GB", "")).isEqualTo("GB"); + assertThat(settings.findRegionIdForTzId(tzId, null, "GB")).isEqualTo("GB"); + } + + @Test + public void createPreferenceControllers_matchExpectedControllers() { + TimeZoneSettings settings = new TimeZoneSettings(); + List controllers = + settings.createPreferenceControllers(RuntimeEnvironment.application); + assertThat(controllers).hasSize(4); + assertThat(controllers.get(0)).isInstanceOf(RegionPreferenceController.class); + assertThat(controllers.get(1)).isInstanceOf(RegionZonePreferenceController.class); + assertThat(controllers.get(2)).isInstanceOf(TimeZoneInfoPreferenceController.class); + assertThat(controllers.get(3)).isInstanceOf(FixedOffsetPreferenceController.class); + } +} From 7cf7fe4f9e4b11194a6fa82f85a452815749a543 Mon Sep 17 00:00:00 2001 From: Victor Chang Date: Wed, 28 Feb 2018 19:43:03 +0000 Subject: [PATCH 07/17] Enable new time zone picker - Change the UI test due to UI changes Bug: 73952488 Test: m RunSettingsRoboTest Test: atest SettingsUITests:ZonePickerSettingsTest Change-Id: I6d5716347e9debf429b757f1edd4118e86b70fab (cherry picked from commit 335c0621d2521d3801c13dafdbfc2e632d171104) --- .../TimeZonePreferenceController.java | 6 +-- .../settings/ui/ZonePickerSettingsTest.java | 46 ++++++++----------- 2 files changed, 22 insertions(+), 30 deletions(-) diff --git a/src/com/android/settings/datetime/TimeZonePreferenceController.java b/src/com/android/settings/datetime/TimeZonePreferenceController.java index e29e24550b3..aff204d76f0 100644 --- a/src/com/android/settings/datetime/TimeZonePreferenceController.java +++ b/src/com/android/settings/datetime/TimeZonePreferenceController.java @@ -19,11 +19,11 @@ package com.android.settings.datetime; import android.content.Context; import android.support.annotation.VisibleForTesting; import android.support.v7.preference.Preference; - import android.util.FeatureFlagUtils; + import com.android.settings.core.FeatureFlags; import com.android.settings.core.PreferenceControllerMixin; -import com.android.settings.datetime.timezone.ZonePicker; +import com.android.settings.datetime.timezone.TimeZoneSettings; import com.android.settingslib.RestrictedPreference; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.datetime.ZoneGetter; @@ -51,7 +51,7 @@ public class TimeZonePreferenceController extends AbstractPreferenceController return; } if (mZonePickerV2) { - preference.setFragment(ZonePicker.class.getName()); + preference.setFragment(TimeZoneSettings.class.getName()); } preference.setSummary(getTimeZoneOffsetAndName()); if( !((RestrictedPreference) preference).isDisabledByAdmin()) { diff --git a/tests/uitests/src/com/android/settings/ui/ZonePickerSettingsTest.java b/tests/uitests/src/com/android/settings/ui/ZonePickerSettingsTest.java index 109c3bc45a2..30206bc5929 100644 --- a/tests/uitests/src/com/android/settings/ui/ZonePickerSettingsTest.java +++ b/tests/uitests/src/com/android/settings/ui/ZonePickerSettingsTest.java @@ -32,8 +32,6 @@ import android.support.test.uiautomator.UiSelector; import android.support.test.uiautomator.Until; import android.system.helpers.SettingsHelper; import android.system.helpers.SettingsHelper.SettingsType; -import android.widget.ListView; -import android.widget.Spinner; import org.junit.After; import org.junit.Before; @@ -102,16 +100,16 @@ public class ZonePickerSettingsTest { // Test 2 time zones with no DST @Test public void testSelectReykjavik() throws Exception { - testSelectTimeZone("Iceland", "Reykjavik", "GMT+00:00", "Atlantic/Reykjavik"); + testSelectTimeZone("Iceland", "Reykjavik", "GMT+00:00", "Atlantic/Reykjavik", true); } @Test public void testSelectPhoenix() throws Exception { - testSelectTimeZone("United States", "Phoenix", "GMT-07:00", "America/Phoenix"); + testSelectTimeZone("United States", "Phoenix", "GMT-07:00", "America/Phoenix", false); } private void testSelectTimeZone(String region, String timezone, String expectedTimeZoneOffset, - String expectedTimeZoneId) throws Exception { + String expectedTimeZoneId, boolean assumeOneTimeZoneInRegion) throws Exception { mHelper.setIntSetting(SettingsType.GLOBAL, Settings.Global.AUTO_TIME_ZONE, 0); SettingsHelper.launchSettingsPage( @@ -121,16 +119,21 @@ public class ZonePickerSettingsTest { assertTrue(selectTimeZone.isEnabled()); selectTimeZone.click(); - // Select region in the dropdown list - selectScrollableItem(selectDropDownInSpinner(By.clazz(Spinner.class)), - new UiSelector().textContains(region)) + wait(By.text("Region")).click(); + // Speed-up the test by searching with the first 2 characters of the region name + wait(By.res("android", "search_src_text")).setText(region.substring(0, 2)); + // Select region in the list + selectItemInList(new UiSelector().textContains(region)) .click(); - // Select time zone - selectScrollableItem(selectTimeZoneList(), - new UiSelector().textContains(timezone)) - .click(); + // Only select time zone explicitly if there are more than one time zones in a region + if (!assumeOneTimeZoneInRegion) { + wait(By.text("Time Zone")); + selectItemInList(new UiSelector().textContains(timezone)) + .click(); + } + mDevice.pressBack(); // The select button should include the GMT offset in the summary BySelector summarySelector = By.res("android:id/summary"); UiObject2 selectedTimeZone = selectTimeZone.findObject(summarySelector); @@ -162,21 +165,10 @@ public class ZonePickerSettingsTest { assertEquals(expectedTimeZoneId, TimeZone.getDefault().getID()); } - /** - * Perform click on {@link Spinner} and return the pop-up dropdown list. - * @return UiScrollable representing the pop-up dropdown after clicking on the spinner - */ - private UiScrollable selectDropDownInSpinner(BySelector spinnerSelector) - throws UiObjectNotFoundException { - UiObject2 spinner = wait(spinnerSelector); - spinner.click(); - - UiSelector dropDownSelector = new UiSelector().className(ListView.class); - return new UiScrollable(dropDownSelector); - } - - private UiScrollable selectTimeZoneList() { - return new UiScrollable(new UiSelector().resourceId(SETTINGS_PACKAGE + ":id/tz_list")); + private UiObject selectItemInList(UiSelector childSelector) throws UiObjectNotFoundException { + UiScrollable recyclerView = new UiScrollable( + new UiSelector().resourceId(SETTINGS_PACKAGE + ":id/recycler_view")); + return selectScrollableItem(recyclerView, childSelector); } /** From 3bf4b9365e1cc97be1087523e312c0a28100a372 Mon Sep 17 00:00:00 2001 From: yuemingw Date: Thu, 1 Feb 2018 13:57:52 +0000 Subject: [PATCH 08/17] Block location accuracy when DISALLOW_CONFIG_LOCATION is set. Bug: 72633181 Test: make ROBOTEST_FILTER=InjectedSettingTest -j40 RunSettingsRoboTests Test: make ROBOTEST_FILTER=LocationServicePreferenceControllerTest -j40 RunSettingsRoboTests Change-Id: I91e4dbff8bcce637424646f5435e72f9bb707631 --- .../settings/location/InjectedSetting.java | 111 +++++++++++----- .../LocationServicePreferenceController.java | 9 +- .../settings/location/SettingsInjector.java | 40 ++++-- .../widget/RestrictedAppPreference.java | 120 ++++++++++++++++++ .../location/InjectedSettingTest.java | 58 +++++++++ ...cationServicePreferenceControllerTest.java | 51 +++++++- 6 files changed, 347 insertions(+), 42 deletions(-) create mode 100644 src/com/android/settings/widget/RestrictedAppPreference.java create mode 100644 tests/robotests/src/com/android/settings/location/InjectedSettingTest.java diff --git a/src/com/android/settings/location/InjectedSetting.java b/src/com/android/settings/location/InjectedSetting.java index e5f1e68a561..7eae872e20e 100644 --- a/src/com/android/settings/location/InjectedSetting.java +++ b/src/com/android/settings/location/InjectedSetting.java @@ -22,7 +22,8 @@ import android.text.TextUtils; import android.util.Log; import com.android.internal.annotations.Immutable; -import com.android.internal.util.Preconditions; + +import java.util.Objects; /** * Specifies a setting that is being injected into Settings > Location > Location services. @@ -65,32 +66,19 @@ class InjectedSetting { */ public final String settingsActivity; - private InjectedSetting(String packageName, String className, - String title, int iconId, UserHandle userHandle, String settingsActivity) { - this.packageName = Preconditions.checkNotNull(packageName, "packageName"); - this.className = Preconditions.checkNotNull(className, "className"); - this.title = Preconditions.checkNotNull(title, "title"); - this.iconId = iconId; - this.mUserHandle = userHandle; - this.settingsActivity = Preconditions.checkNotNull(settingsActivity); - } - /** - * Returns a new instance, or null. + * The user restriction associated with this setting. */ - public static InjectedSetting newInstance(String packageName, String className, - String title, int iconId, UserHandle userHandle, String settingsActivity) { - if (packageName == null || className == null || - TextUtils.isEmpty(title) || TextUtils.isEmpty(settingsActivity)) { - if (Log.isLoggable(SettingsInjector.TAG, Log.WARN)) { - Log.w(SettingsInjector.TAG, "Illegal setting specification: package=" - + packageName + ", class=" + className - + ", title=" + title + ", settingsActivity=" + settingsActivity); - } - return null; - } - return new InjectedSetting(packageName, className, title, iconId, userHandle, - settingsActivity); + public final String userRestriction; + + private InjectedSetting(Builder builder) { + this.packageName = builder.mPackageName; + this.className = builder.mClassName; + this.title = builder.mTitle; + this.iconId = builder.mIconId; + this.mUserHandle = builder.mUserHandle; + this.settingsActivity = builder.mSettingsActivity; + this.userRestriction = builder.mUserRestriction; } @Override @@ -102,6 +90,7 @@ class InjectedSetting { ", iconId=" + iconId + ", userId=" + mUserHandle.getIdentifier() + ", settingsActivity='" + settingsActivity + '\'' + + ", userRestriction='" + userRestriction + '}'; } @@ -121,10 +110,13 @@ class InjectedSetting { InjectedSetting that = (InjectedSetting) o; - return packageName.equals(that.packageName) && className.equals(that.className) - && title.equals(that.title) && iconId == that.iconId - && mUserHandle.equals(that.mUserHandle) - && settingsActivity.equals(that.settingsActivity); + return Objects.equals(packageName, that.packageName) + && Objects.equals(className, that.className) + && Objects.equals(title, that.title) + && Objects.equals(iconId, that.iconId) + && Objects.equals(mUserHandle, that.mUserHandle) + && Objects.equals(settingsActivity, that.settingsActivity) + && Objects.equals(userRestriction, that.userRestriction); } @Override @@ -133,8 +125,67 @@ class InjectedSetting { result = 31 * result + className.hashCode(); result = 31 * result + title.hashCode(); result = 31 * result + iconId; - result = 31 * result + mUserHandle.hashCode(); + result = 31 * result + (mUserHandle == null ? 0 : mUserHandle.hashCode()); result = 31 * result + settingsActivity.hashCode(); + result = 31 * result + (userRestriction == null ? 0 : userRestriction.hashCode()); return result; } + + public static class Builder { + private String mPackageName; + private String mClassName; + private String mTitle; + private int mIconId; + private UserHandle mUserHandle; + private String mSettingsActivity; + private String mUserRestriction; + + public Builder setPackageName(String packageName) { + mPackageName = packageName; + return this; + } + + public Builder setClassName(String className) { + mClassName = className; + return this; + } + + public Builder setTitle(String title) { + mTitle = title; + return this; + } + + public Builder setIconId(int iconId) { + mIconId = iconId; + return this; + } + + public Builder setUserHandle(UserHandle userHandle) { + mUserHandle = userHandle; + return this; + } + + public Builder setSettingsActivity(String settingsActivity) { + mSettingsActivity = settingsActivity; + return this; + } + + public Builder setUserRestriction(String userRestriction) { + mUserRestriction = userRestriction; + return this; + } + + public InjectedSetting build() { + if (mPackageName == null || mClassName == null || TextUtils.isEmpty(mTitle) + || TextUtils.isEmpty(mSettingsActivity)) { + if (Log.isLoggable(SettingsInjector.TAG, Log.WARN)) { + Log.w(SettingsInjector.TAG, "Illegal setting specification: package=" + + mPackageName + ", class=" + mClassName + + ", title=" + mTitle + ", settingsActivity=" + mSettingsActivity); + } + return null; + } + return new InjectedSetting(this); + } + } } diff --git a/src/com/android/settings/location/LocationServicePreferenceController.java b/src/com/android/settings/location/LocationServicePreferenceController.java index 0a6a5c14006..a1d20690dcf 100644 --- a/src/com/android/settings/location/LocationServicePreferenceController.java +++ b/src/com/android/settings/location/LocationServicePreferenceController.java @@ -25,6 +25,7 @@ import android.support.v7.preference.PreferenceCategory; import android.support.v7.preference.PreferenceScreen; import android.util.Log; +import com.android.settings.widget.RestrictedAppPreference; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnPause; @@ -88,7 +89,13 @@ public class LocationServicePreferenceController extends LocationBasePreferenceC @Override public void updateState(Preference preference) { mCategoryLocationServices.removeAll(); - LocationSettings.addPreferencesSorted(getLocationServices(), mCategoryLocationServices); + final List prefs = getLocationServices(); + for (Preference pref : prefs) { + if (pref instanceof RestrictedAppPreference) { + ((RestrictedAppPreference) pref).checkRestrictionAndSetDisabled(); + } + } + LocationSettings.addPreferencesSorted(prefs, mCategoryLocationServices); } @Override diff --git a/src/com/android/settings/location/SettingsInjector.java b/src/com/android/settings/location/SettingsInjector.java index dfa51433820..2c6a4f38c8c 100644 --- a/src/com/android/settings/location/SettingsInjector.java +++ b/src/com/android/settings/location/SettingsInjector.java @@ -20,6 +20,7 @@ import android.app.ActivityManager; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; @@ -37,11 +38,14 @@ import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.support.v7.preference.Preference; +import android.text.TextUtils; import android.util.AttributeSet; +import android.util.IconDrawableFactory; import android.util.Log; import android.util.Xml; import com.android.settings.widget.AppPreference; +import com.android.settings.widget.RestrictedAppPreference; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -215,12 +219,21 @@ class SettingsInjector { sa.getResourceId(android.R.styleable.SettingInjectorService_icon, 0); final String settingsActivity = sa.getString(android.R.styleable.SettingInjectorService_settingsActivity); + final String userRestriction = sa.getString( + android.R.styleable.SettingInjectorService_userRestriction); if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "parsed title: " + title + ", iconId: " + iconId + ", settingsActivity: " + settingsActivity); } - return InjectedSetting.newInstance(packageName, className, - title, iconId, userHandle, settingsActivity); + return new InjectedSetting.Builder() + .setPackageName(packageName) + .setClassName(className) + .setTitle(title) + .setIconId(iconId) + .setUserHandle(userHandle) + .setSettingsActivity(settingsActivity) + .setUserRestriction(userRestriction) + .build(); } finally { sa.recycle(); } @@ -290,15 +303,26 @@ class SettingsInjector { */ private Preference addServiceSetting(Context prefContext, List prefs, InjectedSetting info) { - PackageManager pm = mContext.getPackageManager(); - Drawable appIcon = pm.getDrawable(info.packageName, info.iconId, null); - Drawable icon = pm.getUserBadgedIcon(appIcon, info.mUserHandle); - Preference pref = new AppPreference(prefContext); + final PackageManager pm = mContext.getPackageManager(); + Drawable appIcon = null; + try { + final PackageItemInfo itemInfo = new PackageItemInfo(); + itemInfo.icon = info.iconId; + itemInfo.packageName = info.packageName; + final ApplicationInfo appInfo = pm.getApplicationInfo(info.packageName, + PackageManager.GET_META_DATA); + appIcon = IconDrawableFactory.newInstance(mContext) + .getBadgedIcon(itemInfo, appInfo, info.mUserHandle.getIdentifier()); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Can't get ApplicationInfo for " + info.packageName, e); + } + Preference pref = TextUtils.isEmpty(info.userRestriction) + ? new AppPreference(prefContext) + : new RestrictedAppPreference(prefContext, info.userRestriction); pref.setTitle(info.title); pref.setSummary(null); - pref.setIcon(icon); + pref.setIcon(appIcon); pref.setOnPreferenceClickListener(new ServiceSettingClickedListener(info)); - prefs.add(pref); return pref; } diff --git a/src/com/android/settings/widget/RestrictedAppPreference.java b/src/com/android/settings/widget/RestrictedAppPreference.java new file mode 100644 index 00000000000..af6d8d1e0d4 --- /dev/null +++ b/src/com/android/settings/widget/RestrictedAppPreference.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2018 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.widget; + +import android.content.Context; +import android.os.UserHandle; +import android.support.v7.preference.PreferenceManager; +import android.support.v7.preference.PreferenceViewHolder; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.View; + +import com.android.settings.R; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedPreferenceHelper; + +/** + * {@link AppPreference} that implements user restriction utilities using + * {@link com.android.settingslib.RestrictedPreferenceHelper}. + * Used to show policy transparency on {@link AppPreference}. + */ +public class RestrictedAppPreference extends AppPreference { + private RestrictedPreferenceHelper mHelper; + private String userRestriction; + + public RestrictedAppPreference(Context context) { + super(context); + initialize(null, null); + } + + public RestrictedAppPreference(Context context, String userRestriction) { + super(context); + initialize(null, userRestriction); + } + + public RestrictedAppPreference(Context context, AttributeSet attrs, String userRestriction) { + super(context, attrs); + initialize(attrs, userRestriction); + } + + private void initialize(AttributeSet attrs, String userRestriction) { + setWidgetLayoutResource(R.layout.restricted_icon); + mHelper = new RestrictedPreferenceHelper(getContext(), this, attrs); + this.userRestriction = userRestriction; + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + mHelper.onBindViewHolder(holder); + final View restrictedIcon = holder.findViewById(R.id.restricted_icon); + if (restrictedIcon != null) { + restrictedIcon.setVisibility(isDisabledByAdmin() ? View.VISIBLE : View.GONE); + } + } + + @Override + public void performClick() { + if (!mHelper.performClick()) { + super.performClick(); + } + } + + @Override + public void setEnabled(boolean enabled) { + if (isDisabledByAdmin() && enabled) { + return; + } + super.setEnabled(enabled); + } + + public void setDisabledByAdmin(RestrictedLockUtils.EnforcedAdmin admin) { + if (mHelper.setDisabledByAdmin(admin)) { + notifyChanged(); + } + } + + public boolean isDisabledByAdmin() { + return mHelper.isDisabledByAdmin(); + } + + public void useAdminDisabledSummary(boolean useSummary) { + mHelper.useAdminDisabledSummary(useSummary); + } + + @Override + protected void onAttachedToHierarchy(PreferenceManager preferenceManager) { + mHelper.onAttachedToHierarchy(); + super.onAttachedToHierarchy(preferenceManager); + } + + public void checkRestrictionAndSetDisabled() { + if (TextUtils.isEmpty(userRestriction)) { + return; + } + mHelper.checkRestrictionAndSetDisabled(userRestriction, UserHandle.myUserId()); + } + + public void checkRestrictionAndSetDisabled(String userRestriction) { + mHelper.checkRestrictionAndSetDisabled(userRestriction, UserHandle.myUserId()); + } + + public void checkRestrictionAndSetDisabled(String userRestriction, int userId) { + mHelper.checkRestrictionAndSetDisabled(userRestriction, userId); + } +} diff --git a/tests/robotests/src/com/android/settings/location/InjectedSettingTest.java b/tests/robotests/src/com/android/settings/location/InjectedSettingTest.java new file mode 100644 index 00000000000..fb999582e49 --- /dev/null +++ b/tests/robotests/src/com/android/settings/location/InjectedSettingTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2018 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.location; + +import static com.google.common.truth.Truth.assertThat; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(SettingsRobolectricTestRunner.class) +public final class InjectedSettingTest { + + private static final String TEST_STRING = "test"; + + @Test + public void buildWithoutPackageName_ShouldReturnNull() { + assertThat(((new InjectedSetting.Builder()) + .setClassName(TEST_STRING) + .setTitle(TEST_STRING) + .setSettingsActivity(TEST_STRING).build())).isNull(); + } + + private InjectedSetting getTestSetting() { + return new InjectedSetting.Builder() + .setPackageName(TEST_STRING) + .setClassName(TEST_STRING) + .setTitle(TEST_STRING) + .setSettingsActivity(TEST_STRING).build(); + } + + @Test + public void testEquals() { + InjectedSetting setting1 = getTestSetting(); + InjectedSetting setting2 = getTestSetting(); + assertThat(setting1).isEqualTo(setting2); + } + + @Test + public void testHashCode() { + InjectedSetting setting = getTestSetting(); + assertThat(setting.hashCode()).isEqualTo(1225314048); + } +} diff --git a/tests/robotests/src/com/android/settings/location/LocationServicePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/LocationServicePreferenceControllerTest.java index 195e1b47c6c..099ef7df296 100644 --- a/tests/robotests/src/com/android/settings/location/LocationServicePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/location/LocationServicePreferenceControllerTest.java @@ -24,16 +24,25 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.admin.DevicePolicyManager; import android.arch.lifecycle.LifecycleOwner; +import android.content.ComponentName; import android.content.Context; +import android.os.UserHandle; +import android.os.UserManager; import android.provider.Settings; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceCategory; import android.support.v7.preference.PreferenceScreen; import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.ShadowUserManager; +import com.android.settings.widget.RestrictedAppPreference; import com.android.settingslib.core.lifecycle.Lifecycle; +import java.util.ArrayList; +import java.util.List; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -41,11 +50,13 @@ import org.mockito.Answers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; - -import java.util.ArrayList; -import java.util.List; +import org.robolectric.annotation.Config; @RunWith(SettingsRobolectricTestRunner.class) +@Config( + shadows = { + ShadowUserManager.class + }) public class LocationServicePreferenceControllerTest { @Mock(answer = Answers.RETURNS_DEEP_STUBS) @@ -56,6 +67,8 @@ public class LocationServicePreferenceControllerTest { private PreferenceScreen mScreen; @Mock private SettingsInjector mSettingsInjector; + @Mock + private DevicePolicyManager mDevicePolicyManager; private Context mContext; private LocationServicePreferenceController mController; @@ -73,6 +86,9 @@ public class LocationServicePreferenceControllerTest { final String key = mController.getPreferenceKey(); when(mScreen.findPreference(key)).thenReturn(mCategory); when(mCategory.getKey()).thenReturn(key); + when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)) + .thenReturn(mDevicePolicyManager); + } @Test @@ -132,4 +148,33 @@ public class LocationServicePreferenceControllerTest { verify(mSettingsInjector).reloadStatusMessages(); } + + @Test + public void withUserRestriction_shouldDisableLocationAccuracy() { + final List preferences = new ArrayList<>(); + final RestrictedAppPreference pref = new RestrictedAppPreference(mContext, + UserManager.DISALLOW_CONFIG_LOCATION); + pref.setTitle("Location Accuracy"); + preferences.add(pref); + doReturn(preferences).when(mSettingsInjector) + .getInjectedSettings(any(Context.class), anyInt()); + + int userId = UserHandle.myUserId(); + List enforcingUsers = new ArrayList<>(); + enforcingUsers.add(new UserManager.EnforcingUser(userId, + UserManager.RESTRICTION_SOURCE_DEVICE_OWNER)); + ComponentName componentName = new ComponentName("test", "test"); + // Ensure that RestrictedLockUtils.checkIfRestrictionEnforced doesn't return null. + ShadowUserManager.getShadow().setUserRestrictionSources( + UserManager.DISALLOW_CONFIG_LOCATION, + UserHandle.of(userId), + enforcingUsers); + when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser()).thenReturn(componentName); + + mController.displayPreference(mScreen); + mController.updateState(mCategory); + + assertThat(pref.isEnabled()).isFalse(); + assertThat(pref.isDisabledByAdmin()).isTrue(); + } } From 02caf0f86173f5b109ce19a086adc1728991f817 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Tue, 6 Mar 2018 16:18:10 -0800 Subject: [PATCH 09/17] Misc fixes to improve search - Suppress one of results from "On lock screen" - Remove screen title from sub sections in data usage screen. - On UserSettins, index using resource instead raw. And build non-indexables using getNonIndexable and pref controlleres. Change-Id: I947a97af60f1a08b237fda2f3c456cf1105b9231 Fixes: 74120518 Fixes: 74119743 Fixes: 74250251 Fixes: 74119687 Test: reindex --- res/xml/data_usage_cellular.xml | 3 +- res/xml/data_usage_wifi.xml | 3 +- .../security/LockscreenDashboardFragment.java | 3 +- .../android/settings/users/UserSettings.java | 65 +++++++++---------- 4 files changed, 36 insertions(+), 38 deletions(-) diff --git a/res/xml/data_usage_cellular.xml b/res/xml/data_usage_cellular.xml index 0c1e0141ee1..b50eaba68e6 100644 --- a/res/xml/data_usage_cellular.xml +++ b/res/xml/data_usage_cellular.xml @@ -16,8 +16,7 @@ + android:key="data_usage_cellular_screen"> + android:key="data_usage_wifi_screen"> getNonIndexableKeys(Context context) { final List niks = super.getNonIndexableKeys(context); niks.add(KEY_ADD_USER_FROM_LOCK_SCREEN); + niks.add(KEY_LOCK_SCREEN_NOTIFICATON_WORK_PROFILE); return niks; } }; diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java index 3c16b073c7b..4ca7e63d503 100644 --- a/src/com/android/settings/users/UserSettings.java +++ b/src/com/android/settings/users/UserSettings.java @@ -41,6 +41,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.provider.ContactsContract; +import android.provider.SearchIndexableResource; import android.provider.Settings.Global; import android.support.annotation.VisibleForTesting; import android.support.annotation.WorkerThread; @@ -68,7 +69,6 @@ import com.android.settings.dashboard.SummaryLoader; import com.android.settings.password.ChooseLockGeneric; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Indexable; -import com.android.settings.search.SearchIndexableRaw; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.RestrictedPreference; @@ -203,7 +203,6 @@ public class UserSettings extends SettingsPreferenceFragment final Context context = getActivity(); mAddUserWhenLockedPreferenceController = new AddUserWhenLockedPreferenceController( context, KEY_ADD_USER_WHEN_LOCKED, getLifecycle()); - mAutoSyncDataPreferenceController = new AutoSyncDataPreferenceController(context, this); mAutoSyncPersonalDataPreferenceController = new AutoSyncPersonalDataPreferenceController(context, this); @@ -1186,41 +1185,41 @@ public class UserSettings extends SettingsPreferenceFragment } public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY = - new SummaryLoader.SummaryProviderFactory() { - @Override - public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity, - SummaryLoader summaryLoader) { - return new SummaryProvider(activity, summaryLoader); - } - }; + (activity, summaryLoader) -> new SummaryProvider(activity, summaryLoader); public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new BaseSearchIndexProvider() { - @Override - public List getRawDataToIndex(Context context, - boolean enabled) { - final List result = new ArrayList<>(); - final UserCapabilities userCaps = UserCapabilities.create(context); - if (!userCaps.mEnabled) { - return result; - } - final Resources res = context.getResources(); - SearchIndexableRaw data = new SearchIndexableRaw(context); - data.title = res.getString(R.string.user_settings_title); - data.key = "users_settings"; - data.screenTitle = res.getString(R.string.user_settings_title); - result.add(data); - if (userCaps.mCanAddUser || userCaps.mDisallowAddUserSetByAdmin) { - data = new SearchIndexableRaw(context); - data.title = res.getString(userCaps.mCanAddRestrictedProfile ? - R.string.user_add_user_or_profile_menu - : R.string.user_add_user_menu); - data.screenTitle = res.getString(R.string.user_settings_title); - data.key = "user_settings_add_users"; - result.add(data); - } - return result; + @Override + protected boolean isPageSearchEnabled(Context context) { + final UserCapabilities userCaps = UserCapabilities.create(context); + return userCaps.mEnabled; + } + + @Override + public List getXmlResourcesToIndex(Context context, + boolean enabled) { + final List index = new ArrayList<>(); + // Append the rest of the settings + final SearchIndexableResource sir = new SearchIndexableResource(context); + sir.xmlResId = R.xml.user_settings; + index.add(sir); + return index; + } + + @Override + public List getNonIndexableKeysFromXml(Context context, int xmlResId) { + final List niks = super.getNonIndexableKeysFromXml(context, xmlResId); + new AddUserWhenLockedPreferenceController( + context, KEY_ADD_USER_WHEN_LOCKED, null /* lifecycle */) + .updateNonIndexableKeys(niks); + new AutoSyncDataPreferenceController(context, null /* parent */) + .updateNonIndexableKeys(niks); + new AutoSyncPersonalDataPreferenceController(context, null /* parent */) + .updateNonIndexableKeys(niks); + new AutoSyncWorkDataPreferenceController(context, null /* parent */) + .updateNonIndexableKeys(niks); + return niks; } }; From 30743c787e0919dee18ec100b106c206a7f2fd65 Mon Sep 17 00:00:00 2001 From: Andreas Terzis Date: Wed, 7 Mar 2018 00:26:15 -0800 Subject: [PATCH 10/17] Rename "Billing Cycle" to "App usage cycle". Under Settings > Data Usage, rename "Billing cycle" to "Data warning & limit". In the new "Data warning & limit" screen rename "Billing cycle" to "App usage cycle". Bug: 74321150 Test: manual Change-Id: Ied4fd604fe98f5fc613fd4f7fc3a3bea5908bb7c --- res/values/strings.xml | 7 +++++-- res/xml/billing_cycle.xml | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index e5c7cec78d0..fb39cf01d41 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -8633,8 +8633,11 @@ %1$s Data warning / %2$s Data limit - - Billing cycle + + Data warning & limit + + + App data usage cycle Monthly on day %1$s diff --git a/res/xml/billing_cycle.xml b/res/xml/billing_cycle.xml index 7b619792be3..2338a13f450 100644 --- a/res/xml/billing_cycle.xml +++ b/res/xml/billing_cycle.xml @@ -20,7 +20,7 @@ + android:title="@string/app_usage_cycle" /> Date: Fri, 2 Mar 2018 17:09:48 -0800 Subject: [PATCH 11/17] Update BatteryInfo to include averageTimeToDischarge This updates the Estimate data model class as well as the places it is used to populate the new field when available. Test: robotests Bug: 74020365 Change-Id: Ibcecf933819f8b8cd8514205768569e9bd7d1517 (cherry picked from commit 453db0cdbcb9a721a190218261b24c84ff7727b3) Merged-In: 453db0cdbcb9a721a190218261b24c84ff7727b3 --- .../settings/fuelgauge/BatteryInfo.java | 36 ++++++----- .../settings/fuelgauge/BatteryUtils.java | 13 ++-- .../fuelgauge/DebugEstimatesLoader.java | 6 +- .../android/settings/fuelgauge/Estimate.java | 18 ++++-- .../settings/fuelgauge/BatteryInfoTest.java | 63 +++++++++++-------- 5 files changed, 79 insertions(+), 57 deletions(-) diff --git a/src/com/android/settings/fuelgauge/BatteryInfo.java b/src/com/android/settings/fuelgauge/BatteryInfo.java index 43465ab3c57..20c43230a42 100644 --- a/src/com/android/settings/fuelgauge/BatteryInfo.java +++ b/src/com/android/settings/fuelgauge/BatteryInfo.java @@ -35,18 +35,15 @@ import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.R; import com.android.settingslib.utils.PowerUtil; import com.android.settingslib.utils.StringUtil; -import java.util.concurrent.TimeUnit; public class BatteryInfo { - private static final long SEVEN_MINUTES_MICROS = TimeUnit.MINUTES.toMicros(7); - private static final long FIFTEEN_MINUTES_MICROS = TimeUnit.MINUTES.toMicros(15); - private static final long ONE_DAY_MICROS = TimeUnit.DAYS.toMicros(1); public CharSequence chargeLabel; public CharSequence remainingLabel; public int batteryLevel; public boolean discharging = true; public long remainingTimeUs = 0; + public long averageTimeToDischarge = Estimate.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN; public String batteryPercentString; public String statusLabel; private boolean mCharging; @@ -180,16 +177,18 @@ public class BatteryInfo { BatteryUtils .logRuntime(LOG_TAG, "time for enhanced BatteryInfo", startTime); return BatteryInfo.getBatteryInfo(context, batteryBroadcast, stats, - elapsedRealtimeUs, shortString, - PowerUtil.convertMsToUs(estimate.estimateMillis), - estimate.isBasedOnUsage); + estimate, elapsedRealtimeUs, shortString); } } long prediction = discharging ? stats.computeBatteryTimeRemaining(elapsedRealtimeUs) : 0; + Estimate estimate = new Estimate( + PowerUtil.convertUsToMs(prediction), + false, /* isBasedOnUsage */ + Estimate.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN); BatteryUtils.logRuntime(LOG_TAG, "time for regular BatteryInfo", startTime); return BatteryInfo.getBatteryInfo(context, batteryBroadcast, stats, - elapsedRealtimeUs, shortString, prediction, false); + estimate, elapsedRealtimeUs, shortString); } @Override @@ -204,25 +203,29 @@ public class BatteryInfo { @WorkerThread public static BatteryInfo getBatteryInfoOld(Context context, Intent batteryBroadcast, BatteryStats stats, long elapsedRealtimeUs, boolean shortString) { - return getBatteryInfo(context, batteryBroadcast, stats, elapsedRealtimeUs, shortString, - stats.computeBatteryTimeRemaining(elapsedRealtimeUs), false); + Estimate estimate = new Estimate( + PowerUtil.convertUsToMs(stats.computeBatteryTimeRemaining(elapsedRealtimeUs)), + false, + Estimate.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN); + return getBatteryInfo(context, batteryBroadcast, stats, estimate, elapsedRealtimeUs, + shortString); } @WorkerThread public static BatteryInfo getBatteryInfo(Context context, Intent batteryBroadcast, - BatteryStats stats, long elapsedRealtimeUs, boolean shortString, long drainTimeUs, - boolean basedOnUsage) { + BatteryStats stats, Estimate estimate, long elapsedRealtimeUs, boolean shortString) { final long startTime = System.currentTimeMillis(); BatteryInfo info = new BatteryInfo(); info.mStats = stats; info.batteryLevel = Utils.getBatteryLevel(batteryBroadcast); info.batteryPercentString = Utils.formatPercentage(info.batteryLevel); info.mCharging = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0; + info.averageTimeToDischarge = estimate.averageDischargeTime; final Resources resources = context.getResources(); info.statusLabel = Utils.getBatteryStatus(resources, batteryBroadcast); if (!info.mCharging) { - updateBatteryInfoDischarging(context, shortString, drainTimeUs, basedOnUsage, info); + updateBatteryInfoDischarging(context, shortString, estimate, info); } else { updateBatteryInfoCharging(context, batteryBroadcast, stats, elapsedRealtimeUs, info); } @@ -256,20 +259,21 @@ public class BatteryInfo { } private static void updateBatteryInfoDischarging(Context context, boolean shortString, - long drainTimeUs, boolean basedOnUsage, BatteryInfo info) { + Estimate estimate, BatteryInfo info) { + final long drainTimeUs = PowerUtil.convertMsToUs(estimate.estimateMillis); if (drainTimeUs > 0) { info.remainingTimeUs = drainTimeUs; info.remainingLabel = PowerUtil.getBatteryRemainingStringFormatted( context, PowerUtil.convertUsToMs(drainTimeUs), null /* percentageString */, - basedOnUsage && !shortString + estimate.isBasedOnUsage && !shortString ); info.chargeLabel = PowerUtil.getBatteryRemainingStringFormatted( context, PowerUtil.convertUsToMs(drainTimeUs), info.batteryPercentString, - basedOnUsage && !shortString + estimate.isBasedOnUsage && !shortString ); } else { info.remainingLabel = null; diff --git a/src/com/android/settings/fuelgauge/BatteryUtils.java b/src/com/android/settings/fuelgauge/BatteryUtils.java index 10bc85343e9..3a84026e7c9 100644 --- a/src/com/android/settings/fuelgauge/BatteryUtils.java +++ b/src/com/android/settings/fuelgauge/BatteryUtils.java @@ -438,14 +438,15 @@ public class BatteryUtils { if (estimate != null) { batteryInfo = BatteryInfo.getBatteryInfo(mContext, batteryBroadcast, stats, - elapsedRealtimeUs, false /* shortString */, - PowerUtil.convertMsToUs(estimate.estimateMillis), - estimate.isBasedOnUsage); + estimate, elapsedRealtimeUs, false /* shortString */); } else { + estimate = new Estimate( + PowerUtil.convertUsToMs(stats.computeBatteryTimeRemaining(elapsedRealtimeUs)), + false, + Estimate.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN + ); batteryInfo = BatteryInfo.getBatteryInfo(mContext, batteryBroadcast, stats, - elapsedRealtimeUs, false /* shortString */, - discharging ? stats.computeBatteryTimeRemaining(elapsedRealtimeUs) : 0, - false /* basedOnUsage */); + estimate, elapsedRealtimeUs, false /* shortString */); } BatteryUtils.logRuntime(tag, "BatteryInfoLoader.loadInBackground", startTime); diff --git a/src/com/android/settings/fuelgauge/DebugEstimatesLoader.java b/src/com/android/settings/fuelgauge/DebugEstimatesLoader.java index 509f96758d2..784902fca79 100644 --- a/src/com/android/settings/fuelgauge/DebugEstimatesLoader.java +++ b/src/com/android/settings/fuelgauge/DebugEstimatesLoader.java @@ -58,12 +58,10 @@ public class DebugEstimatesLoader extends AsyncLoader> { Estimate estimate = powerUsageFeatureProvider.getEnhancedBatteryPrediction(context); if (estimate == null) { - estimate = new Estimate(0, false); + estimate = new Estimate(0, false, Estimate.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN); } BatteryInfo newInfo = BatteryInfo.getBatteryInfo(getContext(), batteryBroadcast, stats, - elapsedRealtimeUs, false, - PowerUtil.convertMsToUs(estimate.estimateMillis), - estimate.isBasedOnUsage); + estimate, elapsedRealtimeUs, false); List infos = new ArrayList<>(); infos.add(oldinfo); diff --git a/src/com/android/settings/fuelgauge/Estimate.java b/src/com/android/settings/fuelgauge/Estimate.java index 541678c91fa..f59bbf15ac1 100644 --- a/src/com/android/settings/fuelgauge/Estimate.java +++ b/src/com/android/settings/fuelgauge/Estimate.java @@ -2,11 +2,17 @@ package com.android.settings.fuelgauge; public class Estimate { - public final long estimateMillis; - public final boolean isBasedOnUsage; + // Value to indicate averageTimeToDischarge could not be obtained + public static final int AVERAGE_TIME_TO_DISCHARGE_UNKNOWN = -1; - public Estimate(long estimateMillis, boolean isBasedOnUsage) { - this.estimateMillis = estimateMillis; - this.isBasedOnUsage = isBasedOnUsage; - } + public final long estimateMillis; + public final boolean isBasedOnUsage; + public final long averageDischargeTime; + + public Estimate(long estimateMillis, boolean isBasedOnUsage, + long averageDischargeTime) { + this.estimateMillis = estimateMillis; + this.isBasedOnUsage = isBasedOnUsage; + this.averageDischargeTime = averageDischargeTime; + } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java index bfd117d18c1..654f7fc5a83 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java @@ -63,7 +63,6 @@ public class BatteryInfoTest { private static final String STATUS_CHARGING_NO_TIME = "50% - charging"; private static final String STATUS_CHARGING_TIME = "50% - 0m until fully charged"; private static final String STATUS_NOT_CHARGING = "Not charging"; - private static final int PLUGGED_IN = 1; private static final long REMAINING_TIME_NULL = -1; private static final long REMAINING_TIME = 2; private static final String ENHANCED_STRING_SUFFIX = "based on your usage"; @@ -72,6 +71,11 @@ public class BatteryInfoTest { "1m left until fully charged"; private static final String TEST_BATTERY_LEVEL_10 = "10%"; private static final String FIFTEEN_MIN_FORMATTED = "15m"; + public static final Estimate DUMMY_ESTIMATE = new Estimate( + 1000, /* estimateMillis */ + false, /* isBasedOnUsage */ + 1000 /* averageDischargeTime */); + private Intent mDisChargingBatteryBroadcast; private Intent mChargingBatteryBroadcast; private Context mContext; @@ -132,14 +136,15 @@ public class BatteryInfoTest { @Test public void testGetBatteryInfo_basedOnUsageTrueMoreThanFifteenMinutes_usesCorrectString() { + Estimate estimate = new Estimate(Duration.ofHours(4).toMillis(), + true /* isBasedOnUsage */, + 1000 /* averageDischargeTime */); BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast, - mBatteryStats, SystemClock.elapsedRealtime() * 1000, false /* shortString */, - PowerUtil.convertMsToUs(Duration.ofHours(4).toMillis()), - true /* basedOnUsage */); + mBatteryStats, estimate, SystemClock.elapsedRealtime() * 1000, + false /* shortString */); BatteryInfo info2 = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast, - mBatteryStats, SystemClock.elapsedRealtime() * 1000, true /* shortString */, - PowerUtil.convertMsToUs(Duration.ofHours(4).toMillis()), - true /* basedOnUsage */); + mBatteryStats, estimate, SystemClock.elapsedRealtime() * 1000, + true /* shortString */); // We only add special mention for the long string assertThat(info.remainingLabel.toString()).contains(ENHANCED_STRING_SUFFIX); @@ -149,14 +154,15 @@ public class BatteryInfoTest { @Test public void testGetBatteryInfo_basedOnUsageTrueLessThanSevenMinutes_usesCorrectString() { + Estimate estimate = new Estimate(Duration.ofMinutes(7).toMillis(), + true /* isBasedOnUsage */, + 1000 /* averageDischargeTime */); BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast, - mBatteryStats, SystemClock.elapsedRealtime() * 1000, false /* shortString */, - PowerUtil.convertMsToUs(Duration.ofMinutes(7).toMillis()), - true /* basedOnUsage */); + mBatteryStats, estimate, SystemClock.elapsedRealtime() * 1000, + false /* shortString */); BatteryInfo info2 = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast, - mBatteryStats, SystemClock.elapsedRealtime() * 1000, true /* shortString */, - PowerUtil.convertMsToUs(Duration.ofMinutes(7).toMillis()), - true /* basedOnUsage */); + mBatteryStats, estimate, SystemClock.elapsedRealtime() * 1000, + true /* shortString */); // These should be identical in either case assertThat(info.remainingLabel.toString()).isEqualTo( @@ -167,10 +173,12 @@ public class BatteryInfoTest { @Test public void testGetBatteryInfo_basedOnUsageTrueBetweenSevenAndFifteenMinutes_usesCorrectString() { + Estimate estimate = new Estimate(Duration.ofMinutes(10).toMillis(), + true /* isBasedOnUsage */, + 1000 /* averageDischargeTime */); BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast, - mBatteryStats, SystemClock.elapsedRealtime() * 1000, false /* shortString */, - PowerUtil.convertMsToUs(Duration.ofMinutes(10).toMillis()), - true /* basedOnUsage */); + mBatteryStats, estimate, SystemClock.elapsedRealtime() * 1000, + false /* shortString */); // Check that strings are showing less than 15 minutes remaining regardless of exact time. assertThat(info.chargeLabel.toString()).isEqualTo( @@ -184,11 +192,11 @@ public class BatteryInfoTest { @Test public void testGetBatteryInfo_basedOnUsageFalse_usesDefaultString() { BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast, - mBatteryStats, SystemClock.elapsedRealtime() * 1000, false /* shortString */, - 1000, false /* basedOnUsage */); + mBatteryStats, DUMMY_ESTIMATE, SystemClock.elapsedRealtime() * 1000, + false /* shortString */); BatteryInfo info2 = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast, - mBatteryStats, SystemClock.elapsedRealtime() * 1000, true /* shortString */, - 1000, false /* basedOnUsage */); + mBatteryStats, DUMMY_ESTIMATE, SystemClock.elapsedRealtime() * 1000, + true /* shortString */); assertThat(info.remainingLabel.toString()).doesNotContain(ENHANCED_STRING_SUFFIX); assertThat(info2.remainingLabel.toString()).doesNotContain(ENHANCED_STRING_SUFFIX); @@ -199,8 +207,10 @@ public class BatteryInfoTest { doReturn(TEST_CHARGE_TIME_REMAINING) .when(mBatteryStats) .computeChargeTimeRemaining(anyLong()); + BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mChargingBatteryBroadcast, - mBatteryStats, SystemClock.elapsedRealtime() * 1000, false, 1000, false); + mBatteryStats, DUMMY_ESTIMATE, SystemClock.elapsedRealtime() * 1000, + false /* shortString */); assertThat(info.remainingTimeUs).isEqualTo(TEST_CHARGE_TIME_REMAINING); assertThat(info.remainingLabel.toString()) .isEqualTo(TEST_CHARGE_TIME_REMAINING_STRINGIFIED); @@ -211,8 +221,8 @@ public class BatteryInfoTest { mChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_LEVEL, 100); BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mChargingBatteryBroadcast, - mBatteryStats, SystemClock.elapsedRealtime() * 1000, false /* shortString */, - 1000, false /* basedOnUsage */); + mBatteryStats, DUMMY_ESTIMATE, SystemClock.elapsedRealtime() * 1000, + false /* shortString */); assertThat(info.chargeLabel).isEqualTo("100%"); } @@ -296,10 +306,13 @@ public class BatteryInfoTest { } else { doReturn(0L).when(mBatteryStats).computeChargeTimeRemaining(anyLong()); } + Estimate batteryEstimate = new Estimate( + estimate ? 1000 : 0, + false /* isBasedOnUsage */, + 1000 /* averageDischargeTime */); BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, charging ? mChargingBatteryBroadcast : mDisChargingBatteryBroadcast, - mBatteryStats, SystemClock.elapsedRealtime() * 1000, false, - estimate ? 1000 : 0 /* drainTimeUs */, false); + mBatteryStats, batteryEstimate, SystemClock.elapsedRealtime() * 1000, false); doReturn(enhanced).when(mFeatureFactory.powerUsageFeatureProvider) .isEnhancedBatteryPredictionEnabled(mContext); return info; From 6acc0cf3cdf3e47feb5a9c99adf4e732bfec9f2b Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Wed, 7 Mar 2018 13:19:52 -0800 Subject: [PATCH 12/17] Update string: clear data -> clear storage Change-Id: I710f7b95a53e5437d895ff920f3cce7aa4c5a6cf Fixes: 34515307 Test: visual --- res/values/strings.xml | 10 +++------- .../settings/applications/AppStorageSettings.java | 3 +-- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 45d6e9cf6ac..9b4ba155a22 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -3786,8 +3786,8 @@ Disable Enable - - Clear data + + Clear storage Uninstall updates @@ -3883,12 +3883,8 @@ The app wasn\u2019t found in the list of installed apps. - - Couldn\u2019t clear app data. - - Clear data - Couldn\u2019t clear data for app. + Couldn\u2019t clear storage for app. This app can access the following on your tablet: diff --git a/src/com/android/settings/applications/AppStorageSettings.java b/src/com/android/settings/applications/AppStorageSettings.java index ff036bbd707..7382d201103 100644 --- a/src/com/android/settings/applications/AppStorageSettings.java +++ b/src/com/android/settings/applications/AppStorageSettings.java @@ -28,7 +28,6 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.Loader; -import android.content.UriPermission; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageDataObserver; import android.content.pm.PackageManager; @@ -487,7 +486,7 @@ public class AppStorageSettings extends AppInfoWithHeader .create(); case DLG_CANNOT_CLEAR_DATA: return new AlertDialog.Builder(getActivity()) - .setTitle(getActivity().getText(R.string.clear_failed_dlg_title)) + .setTitle(getActivity().getText(R.string.clear_user_data_text)) .setMessage(getActivity().getText(R.string.clear_failed_dlg_text)) .setNeutralButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { From 10051afb9669f2fb63de87dcae625d5e215cfb34 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Tue, 6 Mar 2018 13:19:15 -0800 Subject: [PATCH 13/17] Add whitelist for anomaly detection. Even though we can add whitelist in config, we still need to have a on device whitelist to reduce the size of config. Use doze whitelist here because we already used it to detect whether we can restrict the app in battery detail page. Bug: 74241534 Test: RunSettingsRoboTests Change-Id: I35b6f3eba9fbc8ae51bb02cd9d5416e4360c388e --- .../AnomalyDetectionJobService.java | 40 +++++----- .../batterytip/BatteryDatabaseManager.java | 13 ++-- .../AnomalyDetectionJobServiceTest.java | 76 +++++++++++++++++++ 3 files changed, 105 insertions(+), 24 deletions(-) diff --git a/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobService.java b/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobService.java index ff715253ad5..83a79bcee1b 100644 --- a/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobService.java +++ b/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobService.java @@ -41,6 +41,7 @@ import android.util.Log; import com.android.internal.os.BatteryStatsHelper; import com.android.settings.R; import com.android.settings.fuelgauge.BatteryUtils; +import com.android.settingslib.fuelgauge.PowerWhitelistBackend; import com.android.settingslib.utils.ThreadUtils; import java.util.List; @@ -81,11 +82,12 @@ public class AnomalyDetectionJobService extends JobService { final BatteryStatsHelper batteryStatsHelper = new BatteryStatsHelper(this, true /* collectBatteryBroadcast */); final UserManager userManager = getSystemService(UserManager.class); + final PowerWhitelistBackend powerWhitelistBackend = PowerWhitelistBackend.getInstance(); for (JobWorkItem item = params.dequeueWork(); item != null; item = params.dequeueWork()) { saveAnomalyToDatabase(batteryStatsHelper, userManager, batteryDatabaseManager, - batteryUtils, policy, contentResolver, + batteryUtils, policy, powerWhitelistBackend, contentResolver, item.getIntent().getExtras()); } jobFinished(params, false /* wantsReschedule */); @@ -102,7 +104,8 @@ public class AnomalyDetectionJobService extends JobService { @VisibleForTesting void saveAnomalyToDatabase(BatteryStatsHelper batteryStatsHelper, UserManager userManager, BatteryDatabaseManager databaseManager, BatteryUtils batteryUtils, - BatteryTipPolicy policy, ContentResolver contentResolver, Bundle bundle) { + BatteryTipPolicy policy, PowerWhitelistBackend powerWhitelistBackend, + ContentResolver contentResolver, Bundle bundle) { // The Example of intentDimsValue is: 35:{1:{1:{1:10013|}|}|} final StatsDimensionsValue intentDimsValue = bundle.getParcelable(StatsManager.EXTRA_STATS_DIMENSIONS_VALUE); @@ -119,24 +122,25 @@ public class AnomalyDetectionJobService extends JobService { final boolean smartBatteryOn = Settings.Global.getInt(contentResolver, Settings.Global.APP_STANDBY_ENABLED, ON) == ON; final String packageName = batteryUtils.getPackageName(uid); - - if (anomalyType == StatsManagerConfig.AnomalyType.EXCESSIVE_BG) { - // TODO(b/72385333): check battery percentage draining in batterystats - if (batteryUtils.isLegacyApp(packageName) && batteryUtils.isAppHeavilyUsed( - batteryStatsHelper, userManager, uid, - policy.excessiveBgDrainPercentage)) { - Log.e(TAG, "Excessive detected uid=" + uid); - batteryUtils.setForceAppStandby(uid, packageName, - AppOpsManager.MODE_IGNORED); + if (!powerWhitelistBackend.isSysWhitelisted(packageName)) { + if (anomalyType == StatsManagerConfig.AnomalyType.EXCESSIVE_BG) { + // TODO(b/72385333): check battery percentage draining in batterystats + if (batteryUtils.isLegacyApp(packageName) && batteryUtils.isAppHeavilyUsed( + batteryStatsHelper, userManager, uid, + policy.excessiveBgDrainPercentage)) { + Log.e(TAG, "Excessive detected uid=" + uid); + batteryUtils.setForceAppStandby(uid, packageName, + AppOpsManager.MODE_IGNORED); + databaseManager.insertAnomaly(uid, packageName, anomalyType, + smartBatteryOn + ? AnomalyDatabaseHelper.State.AUTO_HANDLED + : AnomalyDatabaseHelper.State.NEW, + timeMs); + } + } else { databaseManager.insertAnomaly(uid, packageName, anomalyType, - smartBatteryOn - ? AnomalyDatabaseHelper.State.AUTO_HANDLED - : AnomalyDatabaseHelper.State.NEW, - timeMs); + AnomalyDatabaseHelper.State.NEW, timeMs); } - } else { - databaseManager.insertAnomaly(uid, packageName, anomalyType, - AnomalyDatabaseHelper.State.NEW, timeMs); } } catch (NullPointerException | IndexOutOfBoundsException e) { Log.e(TAG, "Parse stats dimensions value error.", e); diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryDatabaseManager.java b/src/com/android/settings/fuelgauge/batterytip/BatteryDatabaseManager.java index d0bddecffad..798c8c692a0 100644 --- a/src/com/android/settings/fuelgauge/batterytip/BatteryDatabaseManager.java +++ b/src/com/android/settings/fuelgauge/batterytip/BatteryDatabaseManager.java @@ -18,10 +18,10 @@ package com.android.settings.fuelgauge.batterytip; import static com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper.AnomalyColumns .ANOMALY_STATE; -import static com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper.AnomalyColumns - .PACKAGE_NAME; import static com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper.AnomalyColumns .ANOMALY_TYPE; +import static com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper.AnomalyColumns + .PACKAGE_NAME; import static com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper.AnomalyColumns .TIME_STAMP_MS; import static com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper.AnomalyColumns.UID; @@ -61,10 +61,11 @@ public class BatteryDatabaseManager { /** * Insert an anomaly log to database. - * @param packageName the package name of the app - * @param type the type of the anomaly - * @param anomalyState the state of the anomaly - * @param timestampMs the time when it is happened + * + * @param packageName the package name of the app + * @param type the type of the anomaly + * @param anomalyState the state of the anomaly + * @param timestampMs the time when it is happened */ public synchronized void insertAnomaly(int uid, String packageName, int type, int anomalyState, long timestampMs) { diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobServiceTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobServiceTest.java index 77e51b1a8bc..aa3d5a86b90 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobServiceTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobServiceTest.java @@ -17,17 +17,36 @@ package com.android.settings.fuelgauge.batterytip; import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import static org.robolectric.RuntimeEnvironment.application; +import android.app.StatsManager; import android.app.job.JobInfo; import android.app.job.JobScheduler; +import android.content.Context; import android.content.Intent; +import android.os.Bundle; +import android.os.StatsDimensionsValue; +import android.os.UserManager; +import com.android.internal.os.BatteryStatsHelper; import com.android.settings.R; +import com.android.settings.fuelgauge.BatteryUtils; import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.fuelgauge.PowerWhitelistBackend; +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.Shadows; import org.robolectric.shadows.ShadowJobScheduler; @@ -36,6 +55,37 @@ import java.util.concurrent.TimeUnit; @RunWith(SettingsRobolectricTestRunner.class) public class AnomalyDetectionJobServiceTest { + private static final int UID = 123; + private static final String SYSTEM_PACKAGE = "com.android.system"; + @Mock + private BatteryStatsHelper mBatteryStatsHelper; + @Mock + private UserManager mUserManager; + @Mock + private BatteryDatabaseManager mBatteryDatabaseManager; + @Mock + private BatteryUtils mBatteryUtils; + @Mock + private PowerWhitelistBackend mPowerWhitelistBackend; + @Mock + private StatsDimensionsValue mStatsDimensionsValue; + + private BatteryTipPolicy mPolicy; + private Bundle mBundle; + private AnomalyDetectionJobService mAnomalyDetectionJobService; + private Context mContext; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext = RuntimeEnvironment.application; + mPolicy = new BatteryTipPolicy(mContext); + mBundle = new Bundle(); + mBundle.putParcelable(StatsManager.EXTRA_STATS_DIMENSIONS_VALUE, mStatsDimensionsValue); + + mAnomalyDetectionJobService = new AnomalyDetectionJobService(); + } @Test public void testScheduleCleanUp() { @@ -50,4 +100,30 @@ public class AnomalyDetectionJobServiceTest { assertThat(pendingJob.getMaxExecutionDelayMillis()) .isEqualTo(TimeUnit.MINUTES.toMillis(30)); } + + @Test + public void testSaveAnomalyToDatabase_systemWhitelisted_doNotSave() { + doReturn(SYSTEM_PACKAGE).when(mBatteryUtils).getPackageName(anyInt()); + doReturn(true).when(mPowerWhitelistBackend).isSysWhitelisted(SYSTEM_PACKAGE); + + mAnomalyDetectionJobService.saveAnomalyToDatabase(mBatteryStatsHelper, mUserManager, + mBatteryDatabaseManager, mBatteryUtils, mPolicy, mPowerWhitelistBackend, + mContext.getContentResolver(), mBundle); + + verify(mBatteryDatabaseManager, never()).insertAnomaly(anyInt(), anyString(), anyInt(), + anyInt(), anyLong()); + } + + @Test + public void testSaveAnomalyToDatabase_normalApp_save() { + doReturn(SYSTEM_PACKAGE).when(mBatteryUtils).getPackageName(anyInt()); + doReturn(false).when(mPowerWhitelistBackend).isSysWhitelisted(SYSTEM_PACKAGE); + + mAnomalyDetectionJobService.saveAnomalyToDatabase(mBatteryStatsHelper, mUserManager, + mBatteryDatabaseManager, mBatteryUtils, mPolicy, mPowerWhitelistBackend, + mContext.getContentResolver(), mBundle); + + verify(mBatteryDatabaseManager).insertAnomaly(anyInt(), anyString(), anyInt(), anyInt(), + anyLong()); + } } From 9c18ac05545b9cba674a732d6e5effabba971a88 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Wed, 7 Mar 2018 13:36:58 -0800 Subject: [PATCH 14/17] Clean up comment Bug: 73074893 Test: none Change-Id: If7dcfcbb2e943d5734239ddb51c0e6ac798b641b --- src/com/android/settings/core/BasePreferenceController.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/com/android/settings/core/BasePreferenceController.java b/src/com/android/settings/core/BasePreferenceController.java index 9145c3c2efe..391dafcdaaf 100644 --- a/src/com/android/settings/core/BasePreferenceController.java +++ b/src/com/android/settings/core/BasePreferenceController.java @@ -34,8 +34,6 @@ import java.util.List; * Abstract class to consolidate utility between preference controllers and act as an interface * for Slices. The abstract classes that inherit from this class will act as the direct interfaces * for each type when plugging into Slices. - * - * TODO (b/73074893) Add Lifecycle Setting method. */ public abstract class BasePreferenceController extends AbstractPreferenceController { From 36680b0934d65871ddfbc98448da17ad04d525e6 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Wed, 7 Mar 2018 14:15:28 -0800 Subject: [PATCH 15/17] For vibration settings page, create controllers from xml This change leverages a new support in settings framework to automatically share preference controllers between full setting page, search, and slice provider. Bug: 73668763 Test: existing robotest and atest Change-Id: Ie96a28f1b503377b3fdf86e28d297c8560d8bf71 --- res/xml/accessibility_vibration_settings.xml | 14 +++-- .../accessibility/VibrationSettings.java | 30 ---------- .../slices/SliceControllerInXmlTest.java | 57 +++++++------------ 3 files changed, 30 insertions(+), 71 deletions(-) diff --git a/res/xml/accessibility_vibration_settings.xml b/res/xml/accessibility_vibration_settings.xml index d24834a57c3..2228f291832 100644 --- a/res/xml/accessibility_vibration_settings.xml +++ b/res/xml/accessibility_vibration_settings.xml @@ -14,17 +14,21 @@ limitations under the License. --> - + + android:title="@string/accessibility_notification_vibration_title" + app:controller="com.android.settings.accessibility.NotificationVibrationIntensityPreferenceController" /> + android:title="@string/accessibility_touch_vibration_title" + app:controller="com.android.settings.accessibility.HapticFeedbackIntensityPreferenceController" /> diff --git a/src/com/android/settings/accessibility/VibrationSettings.java b/src/com/android/settings/accessibility/VibrationSettings.java index 4b19d93359f..83a5af6ac83 100644 --- a/src/com/android/settings/accessibility/VibrationSettings.java +++ b/src/com/android/settings/accessibility/VibrationSettings.java @@ -23,8 +23,6 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.search.BaseSearchIndexProvider; -import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.core.lifecycle.Lifecycle; import java.util.ArrayList; import java.util.List; @@ -51,28 +49,6 @@ public class VibrationSettings extends DashboardFragment { return TAG; } - @Override - protected List createPreferenceControllers(Context context) { - return buildControllers(context, getLifecycle()); - } - - public static List buildControllers(Context context, - Lifecycle lifecycle) { - - final List controllers = new ArrayList<>(); - final NotificationVibrationIntensityPreferenceController notifVibPrefController = - new NotificationVibrationIntensityPreferenceController(context); - final HapticFeedbackIntensityPreferenceController hapticPreferenceController = - new HapticFeedbackIntensityPreferenceController(context); - controllers.add(hapticPreferenceController); - controllers.add(notifVibPrefController); - if (lifecycle != null) { - lifecycle.addObserver(hapticPreferenceController); - lifecycle.addObserver(notifVibPrefController); - } - return controllers; - } - public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new BaseSearchIndexProvider() { @Override @@ -84,11 +60,5 @@ public class VibrationSettings extends DashboardFragment { indexables.add(indexable); return indexables; } - - @Override - public List createPreferenceControllers( - Context context) { - return buildControllers(context, null /* lifecycle */); - } }; } diff --git a/tests/robotests/src/com/android/settings/slices/SliceControllerInXmlTest.java b/tests/robotests/src/com/android/settings/slices/SliceControllerInXmlTest.java index bf979c6d9f5..7de9c2b2293 100644 --- a/tests/robotests/src/com/android/settings/slices/SliceControllerInXmlTest.java +++ b/tests/robotests/src/com/android/settings/slices/SliceControllerInXmlTest.java @@ -16,16 +16,17 @@ package com.android.settings.slices; +import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_CONTROLLER; +import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.Mockito.spy; import android.content.Context; -import android.content.res.XmlResourceParser; +import android.os.Bundle; import android.provider.SearchIndexableResource; import android.text.TextUtils; -import android.util.AttributeSet; -import android.util.Xml; +import com.android.settings.core.PreferenceXmlParserUtils; import com.android.settings.core.TogglePreferenceController; import com.android.settings.core.codeinspection.ClassScanner; import com.android.settings.core.codeinspection.CodeInspector; @@ -34,7 +35,6 @@ import com.android.settings.search.DatabaseIndexingUtils; import com.android.settings.search.Indexable; import com.android.settings.search.SearchFeatureProvider; import com.android.settings.search.SearchFeatureProviderImpl; -import com.android.settings.core.PreferenceXmlParserUtils; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; @@ -42,8 +42,9 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RuntimeEnvironment; -import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -53,7 +54,7 @@ import java.util.List; public class SliceControllerInXmlTest { private static final List mSliceControllerClasses = Collections.singletonList( - TogglePreferenceController.class + TogglePreferenceController.class ); private final List mXmlDeclaredControllers = new ArrayList<>(); @@ -71,7 +72,7 @@ public class SliceControllerInXmlTest { private FakeFeatureFactory mFakeFeatureFactory; @Before - public void setUp() { + public void setUp() throws IOException, XmlPullParserException { mContext = spy(RuntimeEnvironment.application); mSearchProvider = new SearchFeatureProviderImpl(); @@ -83,44 +84,28 @@ public class SliceControllerInXmlTest { initDeclaredControllers(); } - private void initDeclaredControllers() { + private void initDeclaredControllers() throws IOException, XmlPullParserException { final List xmlResources = getIndexableXml(); - XmlResourceParser parser; - for (int xmlResId : xmlResources) { - try { - parser = mContext.getResources().getXml(xmlResId); - - int type; - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT - && type != XmlPullParser.START_TAG) { - // Parse next until start tag is found + final List metadata = PreferenceXmlParserUtils.extractMetadata(mContext, + xmlResId, PreferenceXmlParserUtils.MetadataFlag.FLAG_NEED_PREF_CONTROLLER); + for (Bundle bundle : metadata) { + final String controllerClassName = bundle.getString(METADATA_CONTROLLER); + if (TextUtils.isEmpty(controllerClassName)) { + continue; } - - final int outerDepth = parser.getDepth(); - final AttributeSet attrs = Xml.asAttributeSet(parser); - String controllerClassName; - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { - continue; - } - controllerClassName = PreferenceXmlParserUtils.getController(mContext, attrs); - - if (!TextUtils.isEmpty(controllerClassName)) { - mXmlDeclaredControllers.add(controllerClassName); - } - } - } catch (Exception e) { - // Assume an issue with robolectric resources + mXmlDeclaredControllers.add(controllerClassName); } } + // We definitely have some controllers in xml, so assert not-empty here as a proxy to + // make sure the parser didn't fail + assertThat(mXmlDeclaredControllers).isNotEmpty(); } @Test public void testAllControllersDeclaredInXml() throws Exception { final List> classes = - new ClassScanner().getClassesForPackage(mContext.getPackageName()); + new ClassScanner().getClassesForPackage(mContext.getPackageName()); final List missingControllersInXml = new ArrayList<>(); for (Class clazz : classes) { @@ -139,7 +124,7 @@ public class SliceControllerInXmlTest { missingControllersInXml.removeAll(mGrandfatheredClasses); final String missingControllerError = - buildErrorMessage(ERROR_MISSING_CONTROLLER, missingControllersInXml); + buildErrorMessage(ERROR_MISSING_CONTROLLER, missingControllersInXml); assertWithMessage(missingControllerError).that(missingControllersInXml).isEmpty(); } From ad47e1aada475325039dca3c4f2d9a61cecd71ea Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Wed, 7 Mar 2018 15:42:53 -0800 Subject: [PATCH 16/17] Move Auto sync data prefs from user to account screen Change-Id: Idb69ed49c6e21f93cafa593ba5e7dffa32f377b7 Fixes: 73739376 Test: existing robotests & atest --- res/xml/accounts_dashboard_settings.xml | 21 +- res/xml/user_settings.xml | 20 -- .../accounts/AccountDashboardFragment.java | 6 + .../android/settings/users/UserSettings.java | 197 ++++++++---------- 4 files changed, 108 insertions(+), 136 deletions(-) diff --git a/res/xml/accounts_dashboard_settings.xml b/res/xml/accounts_dashboard_settings.xml index 1e29d0a7e88..6b5f8259879 100644 --- a/res/xml/accounts_dashboard_settings.xml +++ b/res/xml/accounts_dashboard_settings.xml @@ -23,6 +23,25 @@ + android:order="30"/> + + + + + + diff --git a/res/xml/user_settings.xml b/res/xml/user_settings.xml index 32b46d9df01..7fa735dc727 100644 --- a/res/xml/user_settings.xml +++ b/res/xml/user_settings.xml @@ -16,7 +16,6 @@ @@ -32,25 +31,6 @@ android:icon="@drawable/ic_menu_add" android:order="20"/> - - - - - - > data = new ArrayList>(); - HashMap addUserItem = new HashMap(); + List> data = new ArrayList>(); + HashMap addUserItem = new HashMap(); addUserItem.put(KEY_TITLE, getString(R.string.user_add_user_item_title)); addUserItem.put(KEY_SUMMARY, getString(R.string.user_add_user_item_summary)); - HashMap addProfileItem = new HashMap(); + HashMap addProfileItem = new HashMap(); addProfileItem.put(KEY_TITLE, getString(R.string.user_add_profile_item_title)); addProfileItem.put(KEY_SUMMARY, getString(R.string.user_add_profile_item_summary)); data.add(addUserItem); @@ -891,7 +855,7 @@ public class UserSettings extends SettingsPreferenceFragment // Add a virtual Guest user for guest defaults UserPreference pref = new UserPreference(getPrefContext(), null, UserPreference.USERID_GUEST_DEFAULTS, - mUserCaps.mIsAdmin && voiceCapable? this : null /* settings icon handler */, + mUserCaps.mIsAdmin && voiceCapable ? this : null /* settings icon handler */, null /* delete icon handler */); pref.setTitle(R.string.user_guest); pref.setIcon(getEncircledDefaultIcon()); @@ -1061,20 +1025,20 @@ public class UserSettings extends SettingsPreferenceFragment if (v.getTag() instanceof UserPreference) { int userId = ((UserPreference) v.getTag()).getUserId(); switch (v.getId()) { - case UserPreference.DELETE_ID: - final EnforcedAdmin removeDisallowedAdmin = - RestrictedLockUtils.checkIfRestrictionEnforced(getContext(), - UserManager.DISALLOW_REMOVE_USER, UserHandle.myUserId()); - if (removeDisallowedAdmin != null) { - RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(), - removeDisallowedAdmin); - } else { - onRemoveUserClicked(userId); - } - break; - case UserPreference.SETTINGS_ID: - onManageUserClicked(userId, false); - break; + case UserPreference.DELETE_ID: + final EnforcedAdmin removeDisallowedAdmin = + RestrictedLockUtils.checkIfRestrictionEnforced(getContext(), + UserManager.DISALLOW_REMOVE_USER, UserHandle.myUserId()); + if (removeDisallowedAdmin != null) { + RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(), + removeDisallowedAdmin); + } else { + onRemoveUserClicked(userId); + } + break; + case UserPreference.SETTINGS_ID: + onManageUserClicked(userId, false); + break; } } } @@ -1106,8 +1070,9 @@ public class UserSettings extends SettingsPreferenceFragment * Returns a default user icon (as a {@link Bitmap}) for the given user. * * Note that for guest users, you should pass in {@code UserHandle.USER_NULL}. + * * @param resources resources object to fetch the user icon. - * @param userId the user id or {@code UserHandle.USER_NULL} for a non-user specific icon + * @param userId the user id or {@code UserHandle.USER_NULL} for a non-user specific icon */ private static Bitmap getDefaultUserIconAsBitmap(Resources resources, int userId) { Bitmap bitmap = null; @@ -1124,6 +1089,7 @@ public class UserSettings extends SettingsPreferenceFragment /** * Assign the default photo to user with {@paramref userId} + * * @param context used to get the {@link UserManager} * @param userId used to get the icon bitmap * @return true if assign photo successfully, false if failed @@ -1160,7 +1126,8 @@ public class UserSettings extends SettingsPreferenceFragment um.setUserIcon(userId, icon); try { avatarDataStream.close(); - } catch (IOException ioe) { } + } catch (IOException ioe) { + } } private static class SummaryProvider implements SummaryLoader.SummaryProvider { @@ -1177,9 +1144,9 @@ public class UserSettings extends SettingsPreferenceFragment public void setListening(boolean listening) { if (listening) { UserInfo info = mContext.getSystemService(UserManager.class).getUserInfo( - UserHandle.myUserId()); + UserHandle.myUserId()); mSummaryLoader.setSummary(this, mContext.getString(R.string.users_summary, - info.name)); + info.name)); } } } From c7a0c33ab567aed58141983a4e03002d327a5d9d Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Wed, 28 Feb 2018 17:48:27 -0800 Subject: [PATCH 17/17] Update fuelguage to use uid in AppInfo The uid is needed to distinguish the same app used by different users(even work profile user). So we need to store the correct uid in AppInfo and use it across the settings app. Bug: 74022362 Test: RunSettingsRoboTests Change-Id: Iaa0ea1ac1fbd3a3a1251e0cd1096d6a8c75c4ed8 --- .../RestrictAppPreferenceController.java | 1 + .../fuelgauge/RestrictedAppDetails.java | 8 +- .../fuelgauge/batterytip/AppInfo.java | 17 ++++ .../batterytip/HighUsageAdapter.java | 2 +- .../detectors/HighUsageDetector.java | 1 + .../fuelgauge/RestrictedAppDetailsTest.java | 77 ++++++++++--------- .../detectors/HighUsageDetectorTest.java | 17 +++- 7 files changed, 76 insertions(+), 47 deletions(-) diff --git a/src/com/android/settings/fuelgauge/RestrictAppPreferenceController.java b/src/com/android/settings/fuelgauge/RestrictAppPreferenceController.java index 14330085be9..e41a94b77d8 100644 --- a/src/com/android/settings/fuelgauge/RestrictAppPreferenceController.java +++ b/src/com/android/settings/fuelgauge/RestrictAppPreferenceController.java @@ -74,6 +74,7 @@ public class RestrictAppPreferenceController extends BasePreferenceController { final AppOpsManager.PackageOps packageOps = packageOpsList.get(i); mAppInfos.add(new AppInfo.Builder() .setPackageName(packageOps.getPackageName()) + .setUid(packageOps.getUid()) .build()); } diff --git a/src/com/android/settings/fuelgauge/RestrictedAppDetails.java b/src/com/android/settings/fuelgauge/RestrictedAppDetails.java index 0843c98cddb..88818001307 100644 --- a/src/com/android/settings/fuelgauge/RestrictedAppDetails.java +++ b/src/com/android/settings/fuelgauge/RestrictedAppDetails.java @@ -130,19 +130,15 @@ public class RestrictedAppDetails extends DashboardFragment { appInfo.packageName, 0 /* flags */); checkBoxPreference.setChecked(true); checkBoxPreference.setTitle(mPackageManager.getApplicationLabel(applicationInfo)); - checkBoxPreference.setKey(appInfo.packageName); checkBoxPreference.setIcon( Utils.getBadgedIcon(mIconDrawableFactory, mPackageManager, appInfo.packageName, - UserHandle.getUserId( - mBatteryUtils.getPackageUid(appInfo.packageName)))); + UserHandle.getUserId(appInfo.uid))); checkBoxPreference.setOnPreferenceChangeListener((pref, value) -> { // change the toggle final int mode = (Boolean) value ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED; - final String packageName = pref.getKey(); - final int uid = mBatteryUtils.getPackageUid(packageName); - mBatteryUtils.setForceAppStandby(uid, packageName, mode); + mBatteryUtils.setForceAppStandby(appInfo.uid, appInfo.packageName, mode); return true; }); mRestrictedAppListGroup.addPreference(checkBoxPreference); diff --git a/src/com/android/settings/fuelgauge/batterytip/AppInfo.java b/src/com/android/settings/fuelgauge/batterytip/AppInfo.java index 9557d9aa8e9..dc6ba2521b4 100644 --- a/src/com/android/settings/fuelgauge/batterytip/AppInfo.java +++ b/src/com/android/settings/fuelgauge/batterytip/AppInfo.java @@ -19,6 +19,7 @@ package com.android.settings.fuelgauge.batterytip; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.VisibleForTesting; +import android.text.TextUtils; import com.android.settings.fuelgauge.anomaly.Anomaly; @@ -74,6 +75,22 @@ public class AppInfo implements Comparable, Parcelable { + screenOnTimeMs; } + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof AppInfo)) { + return false; + } + + AppInfo other = (AppInfo) obj; + return anomalyType == other.anomalyType + && uid == other.uid + && screenOnTimeMs == other.screenOnTimeMs + && TextUtils.equals(packageName, other.packageName); + } + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public AppInfo createFromParcel(Parcel in) { return new AppInfo(in); diff --git a/src/com/android/settings/fuelgauge/batterytip/HighUsageAdapter.java b/src/com/android/settings/fuelgauge/batterytip/HighUsageAdapter.java index d982280a4eb..ec45c80f5b1 100644 --- a/src/com/android/settings/fuelgauge/batterytip/HighUsageAdapter.java +++ b/src/com/android/settings/fuelgauge/batterytip/HighUsageAdapter.java @@ -76,7 +76,7 @@ public class HighUsageAdapter extends RecyclerView.Adapter(); - mRestrictedAppDetails.mAppInfos.add(new AppInfo.Builder() - .setPackageName(PACKAGE_NAME) - .build()); - mRestrictedAppDetails.mRestrictedAppListGroup = spy(new PreferenceCategory(mContext)); - mRestrictedAppDetails.mBatteryUtils = new BatteryUtils(mContext); - when(mRestrictedAppDetails.mRestrictedAppListGroup.getPreferenceManager()) - .thenReturn(mPreferenceManager); + doReturn(mPreferenceManager).when(mRestrictedAppDetails).getPreferenceManager(); + doReturn(mContext).when(mPreferenceManager).getContext(); + mRestrictedAppDetails.mPackageManager = mPackageManager; + mRestrictedAppDetails.mIconDrawableFactory = mIconDrawableFactory; + mRestrictedAppDetails.mAppInfos = new ArrayList<>(); + mRestrictedAppDetails.mAppInfos.add(mAppInfo); + mRestrictedAppDetails.mRestrictedAppListGroup = spy(new PreferenceCategory(mContext)); + mRestrictedAppDetails.mBatteryUtils = new BatteryUtils(mContext); + doReturn(mPreferenceManager).when( + mRestrictedAppDetails.mRestrictedAppListGroup).getPreferenceManager(); } @Test public void testRefreshUi_displayPreference() throws Exception { - doReturn(mApplicationInfo).when(mPackageManager).getApplicationInfo(PACKAGE_NAME, 0); - doReturn(APP_NAME).when(mPackageManager).getApplicationLabel(mApplicationInfo); + doReturn(mApplicationInfo).when(mPackageManager).getApplicationInfo(PACKAGE_NAME, 0); + doReturn(APP_NAME).when(mPackageManager).getApplicationLabel(mApplicationInfo); - mRestrictedAppDetails.refreshUi(); + mRestrictedAppDetails.refreshUi(); - assertThat(mRestrictedAppDetails.mRestrictedAppListGroup.getPreferenceCount()).isEqualTo(1); - final Preference preference = mRestrictedAppDetails.mRestrictedAppListGroup.getPreference(0); - assertThat(preference.getKey()).isEqualTo(PACKAGE_NAME); - assertThat(preference.getTitle()).isEqualTo(APP_NAME); + assertThat(mRestrictedAppDetails.mRestrictedAppListGroup.getPreferenceCount()).isEqualTo(1); + final Preference preference = mRestrictedAppDetails.mRestrictedAppListGroup.getPreference( + 0); + assertThat(preference.getTitle()).isEqualTo(APP_NAME); } @Test public void testStartRestrictedAppDetails_startWithCorrectData() { - final ArgumentCaptor captor = ArgumentCaptor.forClass(Intent.class); - doAnswer(invocation -> { - // Get the intent in which it has the app info bundle - mIntent = captor.getValue(); - return true; - }).when(mSettingsActivity).startActivity(captor.capture()); + final ArgumentCaptor captor = ArgumentCaptor.forClass(Intent.class); + doAnswer(invocation -> { + // Get the intent in which it has the app info bundle + mIntent = captor.getValue(); + return true; + }).when(mSettingsActivity).startActivity(captor.capture()); - RestrictedAppDetails. - startRestrictedAppDetails(mSettingsActivity, mFragment, mRestrictedAppDetails.mAppInfos); + RestrictedAppDetails.startRestrictedAppDetails(mSettingsActivity, mFragment, + mRestrictedAppDetails.mAppInfos); - final Bundle bundle = mIntent.getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS); - // Verify the bundle has the correct info - final List appInfos = - bundle.getParcelableArrayList(RestrictedAppDetails.EXTRA_APP_INFO_LIST); - assertThat(appInfos).isNotNull(); - assertThat(appInfos).hasSize(1); - assertThat(appInfos.get(0).packageName).isEqualTo(PACKAGE_NAME); + final Bundle bundle = mIntent.getBundleExtra( + SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS); + // Verify the bundle has the correct info + final List appInfos = bundle.getParcelableArrayList( + RestrictedAppDetails.EXTRA_APP_INFO_LIST); + assertThat(appInfos).containsExactly(mAppInfo); } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetectorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetectorTest.java index 7dbad7acff7..14627c5b63e 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetectorTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetectorTest.java @@ -29,8 +29,10 @@ import android.text.format.DateUtils; import com.android.internal.os.BatterySipper; import com.android.internal.os.BatteryStatsHelper; import com.android.settings.fuelgauge.BatteryUtils; +import com.android.settings.fuelgauge.batterytip.AppInfo; import com.android.settings.fuelgauge.batterytip.BatteryTipPolicy; import com.android.settings.fuelgauge.batterytip.HighUsageDataParser; +import com.android.settings.fuelgauge.batterytip.tips.HighUsageTip; import com.android.settings.testutils.SettingsRobolectricTestRunner; import org.junit.Before; @@ -46,7 +48,8 @@ import java.util.List; @RunWith(SettingsRobolectricTestRunner.class) public class HighUsageDetectorTest { - + private static final int UID = 123; + private static final long SCREEN_ON_TIME_MS = DateUtils.HOUR_IN_MILLIS; private Context mContext; @Mock private BatteryStatsHelper mBatteryStatsHelper; @@ -57,6 +60,7 @@ public class HighUsageDetectorTest { @Mock private HighUsageDataParser mDataParser; + private AppInfo mAppInfo; private BatteryTipPolicy mPolicy; private HighUsageDetector mHighUsageDetector; private List mUsageList; @@ -71,6 +75,11 @@ public class HighUsageDetectorTest { mHighUsageDetector.mBatteryUtils = mBatteryUtils; mHighUsageDetector.mDataParser = mDataParser; doNothing().when(mHighUsageDetector).parseBatteryData(); + doReturn(UID).when(mBatterySipper).getUid(); + mAppInfo = new AppInfo.Builder() + .setUid(UID) + .setScreenOnTimeMs(SCREEN_ON_TIME_MS) + .build(); mUsageList = new ArrayList<>(); mUsageList.add(mBatterySipper); @@ -87,10 +96,12 @@ public class HighUsageDetectorTest { public void testDetect_containsHighUsageApp_tipVisible() { doReturn(true).when(mDataParser).isDeviceHeavilyUsed(); when(mBatteryStatsHelper.getUsageList()).thenReturn(mUsageList); - doReturn(DateUtils.HOUR_IN_MILLIS).when(mBatteryUtils).getProcessTimeMs( + doReturn(SCREEN_ON_TIME_MS).when(mBatteryUtils).getProcessTimeMs( BatteryUtils.StatusType.FOREGROUND, mBatterySipper.uidObj, BatteryStats.STATS_SINCE_CHARGED); - assertThat(mHighUsageDetector.detect().isVisible()).isTrue(); + final HighUsageTip highUsageTip = (HighUsageTip) mHighUsageDetector.detect(); + assertThat(highUsageTip.isVisible()).isTrue(); + assertThat(highUsageTip.getHighUsageAppList()).containsExactly(mAppInfo); } }