diff --git a/res/values/strings.xml b/res/values/strings.xml index e5c7cec78d0..39f641c4e47 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: @@ -7549,10 +7545,10 @@ Alarms - Media + Media - - Includes system feedback like touch and charging sounds + + Touch and charging sounds Reminders @@ -8633,8 +8629,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/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/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/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" /> - + settings:allowDividerAbove="true" + settings:userRestriction="no_config_bluetooth" + settings:useAdminDisabledSummary="true"/> + android:key="data_usage_cellular_screen"> + android:key="data_usage_wifi_screen"> + + + + + + + + + + + + + + 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"/> - - - - - - + settings:initialExpandedChildrenCount="8"> + android:title="@string/zen_mode_media"/> + + + 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/src/com/android/settings/accounts/AccountDashboardFragment.java b/src/com/android/settings/accounts/AccountDashboardFragment.java index cab5dab0c1a..b97694031e6 100644 --- a/src/com/android/settings/accounts/AccountDashboardFragment.java +++ b/src/com/android/settings/accounts/AccountDashboardFragment.java @@ -29,6 +29,9 @@ import com.android.settings.R; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.dashboard.SummaryLoader; import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.users.AutoSyncDataPreferenceController; +import com.android.settings.users.AutoSyncPersonalDataPreferenceController; +import com.android.settings.users.AutoSyncWorkDataPreferenceController; import com.android.settingslib.accounts.AuthenticatorHelper; import com.android.settingslib.core.AbstractPreferenceController; @@ -69,6 +72,9 @@ public class AccountDashboardFragment extends DashboardFragment { new AccountPreferenceController(context, this, authorities); getLifecycle().addObserver(accountPrefController); controllers.add(accountPrefController); + controllers.add(new AutoSyncDataPreferenceController(context, this /*parent */)); + controllers.add(new AutoSyncPersonalDataPreferenceController(context, this /*parent */)); + controllers.add(new AutoSyncWorkDataPreferenceController(context, this /* parent */)); return controllers; } 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) { 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); 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 { 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/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/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/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/AnomalyDetectionJobService.java b/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobService.java index 9d4f86f54c0..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); - databaseManager.insertAnomaly(packageName, anomalyType, - smartBatteryOn - ? AnomalyDatabaseHelper.State.AUTO_HANDLED - : AnomalyDatabaseHelper.State.NEW, - timeMs); + 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, + AnomalyDatabaseHelper.State.NEW, timeMs); } - } else { - databaseManager.insertAnomaly(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/AppInfo.java b/src/com/android/settings/fuelgauge/batterytip/AppInfo.java index 1daff36ab17..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; @@ -33,11 +34,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 +48,7 @@ public class AppInfo implements Comparable, Parcelable { packageName = in.readString(); anomalyType = in.readInt(); screenOnTimeMs = in.readLong(); + uid = in.readInt(); } @Override @@ -62,6 +66,29 @@ public class AppInfo implements Comparable, Parcelable { dest.writeString(packageName); dest.writeInt(anomalyType); dest.writeLong(screenOnTimeMs); + dest.writeInt(uid); + } + + @Override + public String toString() { + return "packageName=" + packageName + ",anomalyType=" + anomalyType + ",screenTime=" + + 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() { @@ -78,6 +105,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 +122,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..798c8c692a0 100644 --- a/src/com/android/settings/fuelgauge/batterytip/BatteryDatabaseManager.java +++ b/src/com/android/settings/fuelgauge/batterytip/BatteryDatabaseManager.java @@ -18,12 +18,13 @@ 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; import static com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper.Tables.TABLE_ANOMALY; import android.content.ContentValues; @@ -60,15 +61,17 @@ 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(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 +86,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 +97,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/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, 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/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/notification/ZenModeBehaviorSettings.java b/src/com/android/settings/notification/ZenModeBehaviorSettings.java index 2dd5ebd8de2..7b5fa048b79 100644 --- a/src/com/android/settings/notification/ZenModeBehaviorSettings.java +++ b/src/com/android/settings/notification/ZenModeBehaviorSettings.java @@ -40,7 +40,8 @@ public class ZenModeBehaviorSettings extends ZenModeSettingsBase implements Inde Lifecycle lifecycle) { List 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/src/com/android/settings/security/LockscreenDashboardFragment.java b/src/com/android/settings/security/LockscreenDashboardFragment.java index c438d8128b6..21b4c921fc6 100644 --- a/src/com/android/settings/security/LockscreenDashboardFragment.java +++ b/src/com/android/settings/security/LockscreenDashboardFragment.java @@ -22,10 +22,10 @@ import android.support.annotation.VisibleForTesting; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; -import com.android.settings.users.AddUserWhenLockedPreferenceController; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.notification.LockScreenNotificationPreferenceController; import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.users.AddUserWhenLockedPreferenceController; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.lifecycle.Lifecycle; @@ -130,6 +130,7 @@ public class LockscreenDashboardFragment extends DashboardFragment public List 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..bc02b3003ff 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; @@ -148,9 +148,6 @@ public class UserSettings extends SettingsPreferenceFragment private EditUserInfoController mEditUserInfoController = new EditUserInfoController(); private AddUserWhenLockedPreferenceController mAddUserWhenLockedPreferenceController; - private AutoSyncDataPreferenceController mAutoSyncDataPreferenceController; - private AutoSyncPersonalDataPreferenceController mAutoSyncPersonalDataPreferenceController; - private AutoSyncWorkDataPreferenceController mAutoSyncWorkDataPreferenceController; // A place to cache the generated default avatar private Drawable mDefaultIconDrawable; @@ -159,15 +156,15 @@ public class UserSettings extends SettingsPreferenceFragment @Override public void handleMessage(Message msg) { switch (msg.what) { - case MESSAGE_UPDATE_LIST: - updateUserList(); - break; - case MESSAGE_SETUP_USER: - onUserCreated(msg.arg1); - break; - case MESSAGE_CONFIG_USER: - onManageUserClicked(msg.arg1, true); - break; + case MESSAGE_UPDATE_LIST: + updateUserList(); + break; + case MESSAGE_SETUP_USER: + onUserCreated(msg.arg1); + break; + case MESSAGE_CONFIG_USER: + onManageUserClicked(msg.arg1, true); + break; } } }; @@ -203,18 +200,9 @@ 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); - mAutoSyncWorkDataPreferenceController = - new AutoSyncWorkDataPreferenceController(context, this); - final PreferenceScreen screen = getPreferenceScreen(); mAddUserWhenLockedPreferenceController.displayPreference(screen); - mAutoSyncDataPreferenceController.displayPreference(screen); - mAutoSyncPersonalDataPreferenceController.displayPreference(screen); - mAutoSyncWorkDataPreferenceController.displayPreference(screen); + screen.findPreference(mAddUserWhenLockedPreferenceController.getPreferenceKey()) .setOnPreferenceChangeListener(mAddUserWhenLockedPreferenceController); @@ -275,22 +263,10 @@ public class UserSettings extends SettingsPreferenceFragment } final PreferenceScreen screen = getPreferenceScreen(); - if (mAutoSyncDataPreferenceController.isAvailable()) { - mAutoSyncDataPreferenceController.updateState(screen.findPreference( - mAutoSyncDataPreferenceController.getPreferenceKey())); - } if (mAddUserWhenLockedPreferenceController.isAvailable()) { mAddUserWhenLockedPreferenceController.updateState(screen.findPreference( mAddUserWhenLockedPreferenceController.getPreferenceKey())); } - if (mAutoSyncPersonalDataPreferenceController.isAvailable()) { - mAutoSyncPersonalDataPreferenceController.updateState(screen.findPreference( - mAutoSyncPersonalDataPreferenceController.getPreferenceKey())); - } - if (mAutoSyncWorkDataPreferenceController.isAvailable()) { - mAutoSyncWorkDataPreferenceController.updateState(screen.findPreference( - mAutoSyncWorkDataPreferenceController.getPreferenceKey())); - } if (mShouldUpdateUserList) { mUserCaps.updateAddUserCapabilities(getActivity()); @@ -330,20 +306,6 @@ public class UserSettings extends SettingsPreferenceFragment super.startActivityForResult(intent, requestCode); } - @Override - public boolean onPreferenceTreeClick(Preference preference) { - if (mAutoSyncDataPreferenceController.handlePreferenceTreeClick(preference)) { - return true; - } - if (mAutoSyncPersonalDataPreferenceController.handlePreferenceTreeClick(preference)) { - return true; - } - if (mAutoSyncWorkDataPreferenceController.handlePreferenceTreeClick(preference)) { - return true; - } - return super.onPreferenceTreeClick(preference); - } - @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { int pos = 0; @@ -439,16 +401,16 @@ public class UserSettings extends SettingsPreferenceFragment synchronized (mUserLock) { if (mRemovingUserId == -1 && !mAddingUser) { switch (userType) { - case USER_TYPE_USER: - showDialog(DIALOG_ADD_USER); - break; - case USER_TYPE_RESTRICTED_PROFILE: - if (hasLockscreenSecurity()) { - addUserNow(USER_TYPE_RESTRICTED_PROFILE); - } else { - showDialog(DIALOG_NEED_LOCKSCREEN); - } - break; + case USER_TYPE_USER: + showDialog(DIALOG_ADD_USER); + break; + case USER_TYPE_RESTRICTED_PROFILE: + if (hasLockscreenSecurity()) { + addUserNow(USER_TYPE_RESTRICTED_PROFILE); + } else { + showDialog(DIALOG_NEED_LOCKSCREEN); + } + break; } } } @@ -557,9 +519,9 @@ public class UserSettings extends SettingsPreferenceFragment } case DIALOG_USER_CANNOT_MANAGE: return new AlertDialog.Builder(context) - .setMessage(R.string.user_cannot_manage_message) - .setPositiveButton(android.R.string.ok, null) - .create(); + .setMessage(R.string.user_cannot_manage_message) + .setPositiveButton(android.R.string.ok, null) + .create(); case DIALOG_ADD_USER: { final SharedPreferences preferences = getActivity().getPreferences( Context.MODE_PRIVATE); @@ -571,55 +533,56 @@ public class UserSettings extends SettingsPreferenceFragment final int userType = dialogId == DIALOG_ADD_USER ? USER_TYPE_USER : USER_TYPE_RESTRICTED_PROFILE; Dialog dlg = new AlertDialog.Builder(context) - .setTitle(R.string.user_add_user_title) - .setMessage(messageResId) - .setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - addUserNow(userType); - if (!longMessageDisplayed) { - preferences.edit().putBoolean( - KEY_ADD_USER_LONG_MESSAGE_DISPLAYED, true).apply(); - } - } - }) - .setNegativeButton(android.R.string.cancel, null) - .create(); + .setTitle(R.string.user_add_user_title) + .setMessage(messageResId) + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + addUserNow(userType); + if (!longMessageDisplayed) { + preferences.edit().putBoolean( + KEY_ADD_USER_LONG_MESSAGE_DISPLAYED, + true).apply(); + } + } + }) + .setNegativeButton(android.R.string.cancel, null) + .create(); return dlg; } case DIALOG_SETUP_USER: { Dialog dlg = new AlertDialog.Builder(context) - .setTitle(R.string.user_setup_dialog_title) - .setMessage(R.string.user_setup_dialog_message) - .setPositiveButton(R.string.user_setup_button_setup_now, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - switchUserNow(mAddedUserId); - } - }) - .setNegativeButton(R.string.user_setup_button_setup_later, null) - .create(); + .setTitle(R.string.user_setup_dialog_title) + .setMessage(R.string.user_setup_dialog_message) + .setPositiveButton(R.string.user_setup_button_setup_now, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + switchUserNow(mAddedUserId); + } + }) + .setNegativeButton(R.string.user_setup_button_setup_later, null) + .create(); return dlg; } case DIALOG_SETUP_PROFILE: { Dialog dlg = new AlertDialog.Builder(context) - .setMessage(R.string.user_setup_profile_dialog_message) - .setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - switchUserNow(mAddedUserId); - } - }) - .setNegativeButton(android.R.string.cancel, null) - .create(); + .setMessage(R.string.user_setup_profile_dialog_message) + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + switchUserNow(mAddedUserId); + } + }) + .setNegativeButton(android.R.string.cancel, null) + .create(); return dlg; } case DIALOG_CHOOSE_USER_TYPE: { - List> 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); @@ -892,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()); @@ -1062,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; } } } @@ -1107,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; @@ -1125,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 @@ -1161,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 { @@ -1178,49 +1144,49 @@ 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)); } } } 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; } }; 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/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); + } +} 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/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; diff --git a/tests/robotests/src/com/android/settings/fuelgauge/RestrictedAppDetailsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/RestrictedAppDetailsTest.java index da4b40a621e..261acad22a2 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/RestrictedAppDetailsTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/RestrictedAppDetailsTest.java @@ -54,6 +54,7 @@ import java.util.List; public class RestrictedAppDetailsTest { private static final String PACKAGE_NAME = "com.android.app"; + private static final int UID = 234; private static final String APP_NAME = "app"; @Mock @@ -70,60 +71,62 @@ public class RestrictedAppDetailsTest { private InstrumentedPreferenceFragment mFragment; private RestrictedAppDetails mRestrictedAppDetails; private Context mContext; + private AppInfo mAppInfo; private Intent mIntent; @Before public void setUp() { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.initMocks(this); - mContext = spy(RuntimeEnvironment.application); - mRestrictedAppDetails = spy(new RestrictedAppDetails()); + mContext = spy(RuntimeEnvironment.application); + mRestrictedAppDetails = spy(new RestrictedAppDetails()); + mAppInfo = new AppInfo.Builder() + .setPackageName(PACKAGE_NAME) + .setUid(UID) + .build(); - when(mRestrictedAppDetails.getPreferenceManager()).thenReturn(mPreferenceManager); - when(mPreferenceManager.getContext()).thenReturn(mContext); - mRestrictedAppDetails.mPackageManager = mPackageManager; - mRestrictedAppDetails.mIconDrawableFactory = mIconDrawableFactory; - mRestrictedAppDetails.mAppInfos = new ArrayList<>(); - 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/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()); + } } 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); } } 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); } } 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 }"); + } } 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(); + } } 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 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(); } 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); } /**