diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 7d47fefc120..bcda6853cb6 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -115,6 +115,7 @@ + - - - - - - - - - - - diff --git a/res/values/strings.xml b/res/values/strings.xml index 7f7c218b020..b839b6d4a8b 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -3542,8 +3542,6 @@ ^1 ^2 - - Used of %1$s %1$s is mounted @@ -13543,6 +13541,9 @@ + + All Services + Show clipboard access diff --git a/res/xml/app_info_settings.xml b/res/xml/app_info_settings.xml index 562c7d17192..bb5fbae7564 100644 --- a/res/xml/app_info_settings.xml +++ b/res/xml/app_info_settings.xml @@ -41,6 +41,12 @@ android:title="@string/app_settings_link" settings:controller="com.android.settings.applications.appinfo.AppSettingPreferenceController" /> + + mHandler.post(r), mTetheringEventCallback); mMassStorageActive = Environment.MEDIA_SHARED.equals(Environment.getExternalStorageState()); registerReceiver(); mEthernetListener = new EthernetListener(); if (mEm != null) - mEm.addListener(mEthernetListener); + mEm.addListener(mEthernetListener, r -> mHandler.post(r)); updateUsbState(); updateBluetoothAndEthernetState(); diff --git a/src/com/android/settings/applications/appinfo/AppAllServicesPreferenceController.java b/src/com/android/settings/applications/appinfo/AppAllServicesPreferenceController.java new file mode 100644 index 00000000000..9444e722217 --- /dev/null +++ b/src/com/android/settings/applications/appinfo/AppAllServicesPreferenceController.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2022 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.applications.appinfo; + +import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.location.LocationManager; +import android.os.Bundle; +import android.util.Log; + +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import java.util.Objects; + +/** + * Preference Controller for the "All Services" preference in the "App Info" page. + */ +public class AppAllServicesPreferenceController extends AppInfoPreferenceControllerBase { + + private static final String TAG = "AllServicesPrefControl"; + private static final String SUMMARY_METADATA_KEY = "app_features_preference_summary"; + + private final PackageManager mPackageManager; + + private String mPackageName; + + private boolean mCanPackageHandleAllServicesIntent; + private boolean mIsLocationProvider; + + + public AppAllServicesPreferenceController(Context context, + String preferenceKey) { + super(context, preferenceKey); + mPackageManager = context.getPackageManager(); + + // Set to false till we can confirm that the package can handle the intent. + mCanPackageHandleAllServicesIntent = false; + // Set to false till we can confirm that the package is a location provider. + mIsLocationProvider = false; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + CharSequence summary = getStorageSummary(); + if (summary != null) { + mPreference.setSummary(summary); + } + } + + @VisibleForTesting + @Nullable + CharSequence getStorageSummary() { + ResolveInfo resolveInfo = getResolveInfo(PackageManager.GET_META_DATA); + if (resolveInfo == null) { + Log.d(TAG, "mResolveInfo is null."); + return null; + } + final Bundle metaData = resolveInfo.activityInfo.metaData; + if (metaData != null) { + try { + final Resources pkgRes = mPackageManager.getResourcesForActivity( + new ComponentName(mPackageName, resolveInfo.activityInfo.name)); + return pkgRes.getString(metaData.getInt(SUMMARY_METADATA_KEY)); + } catch (Resources.NotFoundException exception) { + Log.d(TAG, "Resource not found for summary string."); + } catch (PackageManager.NameNotFoundException exception) { + Log.d(TAG, "Name of resource not found for summary string."); + } + } + return null; + } + + @Override + public int getAvailabilityStatus() { + if (mCanPackageHandleAllServicesIntent && mIsLocationProvider) { + return AVAILABLE; + } + return CONDITIONALLY_UNAVAILABLE; + } + + private boolean isLocationProvider() { + return Objects.requireNonNull( + mContext.getSystemService(LocationManager.class)).isProviderPackage(mPackageName); + } + + private boolean canPackageHandleIntent() { + return getResolveInfo(0) != null; + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (getPreferenceKey().equals(preference.getKey())) { + startAllServicesActivity(); + return true; + } + return false; + } + + /** + * Set the package name of the package for which the "All Services" activity needs to be shown. + * + * @param packageName Name of the package for which the services need to be shown. + */ + public void setPackageName(String packageName) { + mPackageName = packageName; + + //Once we have package name. Update conditions for availability. + updateAvailabilityConditions(); + } + + private void updateAvailabilityConditions() { + mCanPackageHandleAllServicesIntent = canPackageHandleIntent(); + mIsLocationProvider = isLocationProvider(); + } + + private void startAllServicesActivity() { + final Intent featuresIntent = new Intent(Intent.ACTION_VIEW_APP_FEATURES); + // This won't be null since the preference is only shown for packages that can handle the + // intent. + ResolveInfo resolveInfo = getResolveInfo(0); + featuresIntent.setComponent( + new ComponentName(mPackageName, resolveInfo.activityInfo.name)); + + Activity activity = mParent.getActivity(); + try { + if (activity != null) { + activity.startActivity(featuresIntent); + } + } catch (ActivityNotFoundException e) { + Log.e(TAG, "The app cannot handle android.intent.action.VIEW_APP_FEATURES"); + } + } + + @Nullable + private ResolveInfo getResolveInfo(int flags) { + if (mPackageName == null) { + return null; + } + final Intent featuresIntent = new Intent(Intent.ACTION_VIEW_APP_FEATURES); + featuresIntent.setPackage(mPackageName); + + return mPackageManager.resolveActivity(featuresIntent, flags); + } +} diff --git a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java index 243dc564be8..6d22dd03021 100755 --- a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java +++ b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java @@ -162,6 +162,8 @@ public class AppInfoDashboardFragment extends DashboardFragment use(AppSettingPreferenceController.class) .setPackageName(packageName) .setParentFragment(this); + use(AppAllServicesPreferenceController.class).setParentFragment(this); + use(AppAllServicesPreferenceController.class).setPackageName(packageName); use(AppStoragePreferenceController.class).setParentFragment(this); use(AppVersionPreferenceController.class).setParentFragment(this); use(InstantAppDomainsPreferenceController.class).setParentFragment(this); diff --git a/src/com/android/settings/deviceinfo/PublicVolumeSettings.java b/src/com/android/settings/deviceinfo/PublicVolumeSettings.java index 5315347afbc..f7dd85ab67a 100644 --- a/src/com/android/settings/deviceinfo/PublicVolumeSettings.java +++ b/src/com/android/settings/deviceinfo/PublicVolumeSettings.java @@ -28,8 +28,6 @@ import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; import android.os.storage.VolumeRecord; import android.provider.DocumentsContract; -import android.text.TextUtils; -import android.text.format.Formatter; import android.view.View; import android.view.ViewGroup; import android.widget.Button; @@ -40,8 +38,10 @@ import androidx.preference.PreferenceScreen; import com.android.internal.util.Preconditions; import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.deviceinfo.storage.StorageUtils; import com.android.settings.deviceinfo.storage.StorageUtils.MountTask; import com.android.settings.deviceinfo.storage.StorageUtils.UnmountTask; +import com.android.settingslib.widget.UsageProgressBarPreference; import java.io.File; import java.util.Objects; @@ -59,7 +59,7 @@ public class PublicVolumeSettings extends SettingsPreferenceFragment { private VolumeInfo mVolume; private DiskInfo mDisk; - private StorageSummaryPreference mSummary; + private UsageProgressBarPreference mSummary; private Preference mMount; private Preference mFormatPublic; @@ -114,7 +114,7 @@ public class PublicVolumeSettings extends SettingsPreferenceFragment { addPreferencesFromResource(R.xml.device_info_storage_volume); getPreferenceScreen().setOrderingAsAdded(true); - mSummary = new StorageSummaryPreference(getPrefContext()); + mSummary = new UsageProgressBarPreference(getPrefContext()); mMount = buildAction(R.string.storage_menu_mount); mUnmount = new Button(getActivity()); @@ -162,12 +162,10 @@ public class PublicVolumeSettings extends SettingsPreferenceFragment { final long freeBytes = file.getFreeSpace(); final long usedBytes = totalBytes - freeBytes; - final Formatter.BytesResult result = Formatter.formatBytes(getResources(), usedBytes, - 0); - mSummary.setTitle(TextUtils.expandTemplate(getText(R.string.storage_size_large), - result.value, result.units)); - mSummary.setSummary(getString(R.string.storage_volume_used, - Formatter.formatFileSize(context, totalBytes))); + mSummary.setUsageSummary(StorageUtils.getStorageSummary( + context, R.string.storage_usage_summary, usedBytes)); + mSummary.setTotalSummary(StorageUtils.getStorageSummary( + context, R.string.storage_total_summary, totalBytes)); mSummary.setPercent(usedBytes, totalBytes); } diff --git a/src/com/android/settings/deviceinfo/StorageSummaryPreference.java b/src/com/android/settings/deviceinfo/StorageSummaryPreference.java deleted file mode 100644 index 72b67de1ddd..00000000000 --- a/src/com/android/settings/deviceinfo/StorageSummaryPreference.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2015 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.deviceinfo; - -import android.content.Context; -import android.graphics.Color; -import android.util.MathUtils; -import android.view.View; -import android.widget.ProgressBar; -import android.widget.TextView; - -import androidx.preference.Preference; -import androidx.preference.PreferenceViewHolder; - -import com.android.settings.R; - -public class StorageSummaryPreference extends Preference { - private int mPercent = -1; - - public StorageSummaryPreference(Context context) { - super(context); - - setLayoutResource(R.layout.storage_summary); - setEnabled(false); - } - - public void setPercent(long usedBytes, long totalBytes) { - mPercent = MathUtils.constrain((int) ((usedBytes * 100) / totalBytes), - (usedBytes > 0) ? 1 : 0, 100); - } - - @Override - public void onBindViewHolder(PreferenceViewHolder view) { - final ProgressBar progress = (ProgressBar) view.findViewById(android.R.id.progress); - if (mPercent != -1) { - progress.setVisibility(View.VISIBLE); - progress.setProgress(mPercent); - progress.setScaleY(7f); - } else { - progress.setVisibility(View.GONE); - } - - final TextView summary = (TextView) view.findViewById(android.R.id.summary); - summary.setTextColor(Color.parseColor("#8a000000")); - - super.onBindViewHolder(view); - } -} diff --git a/src/com/android/settings/deviceinfo/storage/StorageUsageProgressBarPreferenceController.java b/src/com/android/settings/deviceinfo/storage/StorageUsageProgressBarPreferenceController.java index cfee6a435b7..766035307e9 100644 --- a/src/com/android/settings/deviceinfo/storage/StorageUsageProgressBarPreferenceController.java +++ b/src/com/android/settings/deviceinfo/storage/StorageUsageProgressBarPreferenceController.java @@ -18,7 +18,6 @@ package com.android.settings.deviceinfo.storage; import android.app.usage.StorageStatsManager; import android.content.Context; -import android.text.format.Formatter; import android.util.Log; import androidx.annotation.VisibleForTesting; @@ -113,16 +112,10 @@ public class StorageUsageProgressBarPreferenceController extends BasePreferenceC return; } mIsUpdateStateFromSelectedStorageEntry = false; - mUsageProgressBarPreference.setUsageSummary( - getStorageSummary(R.string.storage_usage_summary, mUsedBytes)); - mUsageProgressBarPreference.setTotalSummary( - getStorageSummary(R.string.storage_total_summary, mTotalBytes)); + mUsageProgressBarPreference.setUsageSummary(StorageUtils.getStorageSummary( + mContext, R.string.storage_usage_summary, mUsedBytes)); + mUsageProgressBarPreference.setTotalSummary(StorageUtils.getStorageSummary( + mContext, R.string.storage_total_summary, mTotalBytes)); mUsageProgressBarPreference.setPercent(mUsedBytes, mTotalBytes); } - - private String getStorageSummary(int resId, long bytes) { - final Formatter.BytesResult result = Formatter.formatBytes(mContext.getResources(), - bytes, Formatter.FLAG_SHORTER); - return mContext.getString(resId, result.value, result.units); - } } diff --git a/src/com/android/settings/deviceinfo/storage/StorageUtils.java b/src/com/android/settings/deviceinfo/storage/StorageUtils.java index 9b52fe803b1..f59c8ab50d1 100644 --- a/src/com/android/settings/deviceinfo/storage/StorageUtils.java +++ b/src/com/android/settings/deviceinfo/storage/StorageUtils.java @@ -199,7 +199,7 @@ public class StorageUtils { } } - /* Shows information about system storage. */ + /** Shows information about system storage. */ public static class SystemInfoFragment extends InstrumentedDialogFragment { /** Shows the fragment. */ public static void show(Fragment parent) { @@ -224,4 +224,11 @@ public class StorageUtils { .create(); } } + + /** Gets a summary which has a byte size information. */ + public static String getStorageSummary(Context context, int resId, long bytes) { + final Formatter.BytesResult result = Formatter.formatBytes(context.getResources(), + bytes, Formatter.FLAG_SHORTER); + return context.getString(resId, result.value, result.units); + } } diff --git a/src/com/android/settings/display/AutoRotateSwitchBarController.java b/src/com/android/settings/display/AutoRotateSwitchBarController.java index e149ccb6c76..48dedfd1fa3 100644 --- a/src/com/android/settings/display/AutoRotateSwitchBarController.java +++ b/src/com/android/settings/display/AutoRotateSwitchBarController.java @@ -31,7 +31,7 @@ import com.android.settingslib.core.lifecycle.events.OnStop; import com.android.settingslib.widget.OnMainSwitchChangeListener; /** - * The switch controller for the location. + * The switch controller for auto-rotate. */ public class AutoRotateSwitchBarController implements OnMainSwitchChangeListener, LifecycleObserver, OnStart, OnStop { diff --git a/src/com/android/settings/display/SmartAutoRotateController.java b/src/com/android/settings/display/SmartAutoRotateController.java index b88aa807f97..76a222aac75 100644 --- a/src/com/android/settings/display/SmartAutoRotateController.java +++ b/src/com/android/settings/display/SmartAutoRotateController.java @@ -63,7 +63,7 @@ public class SmartAutoRotateController extends TogglePreferenceController implem updateState(mPreference); } }; - private Preference mPreference; + protected Preference mPreference; private RotationPolicy.RotationPolicyListener mRotationPolicyListener; public SmartAutoRotateController(Context context, String preferenceKey) { @@ -84,10 +84,14 @@ public class SmartAutoRotateController extends TogglePreferenceController implem if (!isRotationResolverServiceAvailable(mContext)) { return UNSUPPORTED_ON_DEVICE; } - return !RotationPolicy.isRotationLocked(mContext) && hasSufficientPermission(mContext) + return !isRotationLocked() && hasSufficientPermission(mContext) && !isCameraLocked() && !isPowerSaveMode() ? AVAILABLE : DISABLED_DEPENDENT_SETTING; } + protected boolean isRotationLocked() { + return RotationPolicy.isRotationLocked(mContext); + } + @Override public void updateState(Preference preference) { super.updateState(preference); @@ -136,7 +140,7 @@ public class SmartAutoRotateController extends TogglePreferenceController implem @Override public boolean isChecked() { - return !RotationPolicy.isRotationLocked(mContext) && hasSufficientPermission(mContext) + return !isRotationLocked() && hasSufficientPermission(mContext) && !isCameraLocked() && !isPowerSaveMode() && Settings.Secure.getInt( mContext.getContentResolver(), CAMERA_AUTOROTATE, 0) == 1; @@ -163,7 +167,10 @@ public class SmartAutoRotateController extends TogglePreferenceController implem return R.string.menu_key_display; } - static boolean isRotationResolverServiceAvailable(Context context) { + /** + * Returns true if there is a {@link RotationResolverService} available + */ + public static boolean isRotationResolverServiceAvailable(Context context) { final PackageManager packageManager = context.getPackageManager(); final String resolvePackage = packageManager.getRotationResolverPackageName(); if (TextUtils.isEmpty(resolvePackage)) { diff --git a/src/com/android/settings/inputmethod/AvailableVirtualKeyboardFragment.java b/src/com/android/settings/inputmethod/AvailableVirtualKeyboardFragment.java index b083aefee0b..8bef7086817 100644 --- a/src/com/android/settings/inputmethod/AvailableVirtualKeyboardFragment.java +++ b/src/com/android/settings/inputmethod/AvailableVirtualKeyboardFragment.java @@ -27,6 +27,7 @@ import android.provider.SearchIndexableResource; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; +import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.dashboard.DashboardFragment; @@ -43,16 +44,17 @@ import java.util.List; /** * The fragment for on-screen keyboard settings which used to display user installed IMEs. - * - * TODO(b/207452897): Add test for AvailableVirtualKeyboardFragment */ @SearchIndexable -public final class AvailableVirtualKeyboardFragment extends DashboardFragment +public class AvailableVirtualKeyboardFragment extends DashboardFragment implements InputMethodPreference.OnSavePreferenceListener { private static final String TAG = "AvailableVirtualKeyboardFragment"; - private final ArrayList mInputMethodPreferenceList = new ArrayList<>(); - private InputMethodSettingValuesWrapper mInputMethodSettingValues; + @VisibleForTesting + final ArrayList mInputMethodPreferenceList = new ArrayList<>(); + + @VisibleForTesting + InputMethodSettingValuesWrapper mInputMethodSettingValues; private Context mUserAwareContext; private int mUserId; @@ -118,7 +120,8 @@ public final class AvailableVirtualKeyboardFragment extends DashboardFragment return SettingsEnums.ENABLE_VIRTUAL_KEYBOARDS; } - private void updateInputMethodPreferenceViews() { + @VisibleForTesting + void updateInputMethodPreferenceViews() { mInputMethodSettingValues.refreshAllInputMethodAndSubtypes(); // Clear existing "InputMethodPreference"s mInputMethodPreferenceList.clear(); diff --git a/src/com/android/settings/network/EthernetTetherPreferenceController.java b/src/com/android/settings/network/EthernetTetherPreferenceController.java index 06cd6a7feed..5b2cab752be 100644 --- a/src/com/android/settings/network/EthernetTetherPreferenceController.java +++ b/src/com/android/settings/network/EthernetTetherPreferenceController.java @@ -48,13 +48,12 @@ public final class EthernetTetherPreferenceController extends TetherBasePreferen @OnLifecycleEvent(Lifecycle.Event.ON_START) public void onStart() { - mEthernetListener = new EthernetManager.Listener() { - @Override - public void onAvailabilityChanged(String iface, boolean isAvailable) { - new Handler(Looper.getMainLooper()).post(() -> updateState(mPreference)); - } - }; - mEthernetManager.addListener(mEthernetListener); + mEthernetListener = (iface, isAvailable) -> updateState(mPreference); + final Handler handler = new Handler(Looper.getMainLooper()); + // Executor will execute to post the updateState event to a new handler which is created + // from the main looper when the {@link EthernetManager.Listener.onAvailabilityChanged} + // is triggerd. + mEthernetManager.addListener(mEthernetListener, r -> handler.post(r)); } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) diff --git a/src/com/android/settings/notification/zen/ZenModeScheduleRuleSettings.java b/src/com/android/settings/notification/zen/ZenModeScheduleRuleSettings.java index 5b02b3b0c18..d46d31a4860 100644 --- a/src/com/android/settings/notification/zen/ZenModeScheduleRuleSettings.java +++ b/src/com/android/settings/notification/zen/ZenModeScheduleRuleSettings.java @@ -42,7 +42,6 @@ import com.android.settings.R; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.android.settingslib.core.AbstractPreferenceController; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -56,8 +55,7 @@ public class ZenModeScheduleRuleSettings extends ZenModeRuleSettingsBase { public static final String ACTION = Settings.ACTION_ZEN_MODE_SCHEDULE_RULE_SETTINGS; - // per-instance to ensure we're always using the current locale - private final SimpleDateFormat mDayFormat = new SimpleDateFormat("EEE"); + private final ZenRuleScheduleHelper mScheduleHelper = new ZenRuleScheduleHelper(); private Preference mDays; private TimePickerPreference mStart; @@ -149,30 +147,11 @@ public class ZenModeScheduleRuleSettings extends ZenModeRuleSettingsBase { } private void updateDays() { - // Compute an ordered, delimited list of day names based on the persisted user config. - final int[] days = mSchedule.days; - if (days != null && days.length > 0) { - final StringBuilder sb = new StringBuilder(); - final Calendar c = Calendar.getInstance(); - int[] daysOfWeek = ZenModeScheduleDaysSelection.getDaysOfWeekForLocale(c); - for (int i = 0; i < daysOfWeek.length; i++) { - final int day = daysOfWeek[i]; - for (int j = 0; j < days.length; j++) { - if (day == days[j]) { - c.set(Calendar.DAY_OF_WEEK, day); - if (sb.length() > 0) { - sb.append(mContext.getString(R.string.summary_divider_text)); - } - sb.append(mDayFormat.format(c.getTime())); - break; - } - } - } - if (sb.length() > 0) { - mDays.setSummary(sb); - mDays.notifyDependencyChange(false); - return; - } + String desc = mScheduleHelper.getDaysDescription(mContext, mSchedule); + if (desc != null) { + mDays.setSummary(desc); + mDays.notifyDependencyChange(false); + return; } mDays.setSummary(R.string.zen_mode_schedule_rule_days_none); mDays.notifyDependencyChange(true); diff --git a/src/com/android/settings/notification/zen/ZenRulePreference.java b/src/com/android/settings/notification/zen/ZenRulePreference.java index a265a0776d2..8cf3106200b 100644 --- a/src/com/android/settings/notification/zen/ZenRulePreference.java +++ b/src/com/android/settings/notification/zen/ZenRulePreference.java @@ -23,22 +23,20 @@ import android.content.Intent; import android.content.pm.ComponentInfo; import android.content.pm.PackageManager; import android.service.notification.ZenModeConfig; -import android.view.View; -import android.widget.CheckBox; +import android.service.notification.ZenModeConfig.ScheduleInfo; import androidx.fragment.app.Fragment; import androidx.preference.Preference; -import androidx.preference.PreferenceViewHolder; import com.android.settings.R; import com.android.settings.utils.ManagedServiceSettings; import com.android.settings.utils.ZenServiceListing; +import com.android.settingslib.PrimarySwitchPreference; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; -import com.android.settingslib.widget.TwoTargetPreference; import java.util.Map; -public class ZenRulePreference extends TwoTargetPreference { +public class ZenRulePreference extends PrimarySwitchPreference { private static final ManagedServiceSettings.Config CONFIG = ZenModeAutomationSettings.getConditionProviderConfig(); final String mId; @@ -53,14 +51,13 @@ public class ZenRulePreference extends TwoTargetPreference { CharSequence mName; private Intent mIntent; - private boolean mChecked; - private CheckBox mCheckBox; + + private final ZenRuleScheduleHelper mScheduleHelper = new ZenRuleScheduleHelper(); public ZenRulePreference(Context context, final Map.Entry ruleEntry, Fragment parent, MetricsFeatureProvider metricsProvider) { super(context); - setLayoutResource(R.layout.preference_checkable_two_target); mBackend = ZenModeBackend.getInstance(context); mContext = context; mRule = ruleEntry.getValue(); @@ -72,50 +69,11 @@ public class ZenRulePreference extends TwoTargetPreference { mServiceListing.reloadApprovedServices(); mPref = this; mMetricsFeatureProvider = metricsProvider; - mChecked = mRule.isEnabled(); setAttributes(mRule); setWidgetLayoutResource(getSecondTargetResId()); - } - protected int getSecondTargetResId() { - if (mIntent != null) { - return R.layout.zen_rule_widget; - } - return 0; - } - - @Override - public void onBindViewHolder(PreferenceViewHolder view) { - super.onBindViewHolder(view); - View settingsWidget = view.findViewById(android.R.id.widget_frame); - View divider = view.findViewById(R.id.two_target_divider); - if (mIntent != null) { - divider.setVisibility(View.VISIBLE); - settingsWidget.setVisibility(View.VISIBLE); - settingsWidget.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mContext.startActivity(mIntent); - } - }); - } else { - divider.setVisibility(View.GONE); - settingsWidget.setVisibility(View.GONE); - settingsWidget.setOnClickListener(null); - } - - View checkboxContainer = view.findViewById(R.id.checkbox_container); - if (checkboxContainer != null) { - checkboxContainer.setOnClickListener(mOnCheckBoxClickListener); - } - mCheckBox = (CheckBox) view.findViewById(com.android.internal.R.id.checkbox); - if (mCheckBox != null) { - mCheckBox.setChecked(mChecked); - } - } - - public boolean isChecked() { - return mChecked; + // initialize the checked state of the preference + super.setChecked(mRule.isEnabled()); } public void updatePreference(AutomaticZenRule rule) { @@ -126,34 +84,24 @@ public class ZenRulePreference extends TwoTargetPreference { if (mRule.isEnabled() != rule.isEnabled()) { setChecked(rule.isEnabled()); - setSummary(computeRuleSummary(rule)); } - + setSummary(computeRuleSummary(rule)); mRule = rule; } @Override public void onClick() { - mOnCheckBoxClickListener.onClick(null); + mContext.startActivity(mIntent); } - private void setChecked(boolean checked) { - mChecked = checked; - if (mCheckBox != null) { - mCheckBox.setChecked(checked); - } + @Override + public void setChecked(boolean checked) { + mRule.setEnabled(checked); + mBackend.updateZenRule(mId, mRule); + setAttributes(mRule); + super.setChecked(checked); } - private View.OnClickListener mOnCheckBoxClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - mRule.setEnabled(!mChecked); - mBackend.updateZenRule(mId, mRule); - setChecked(mRule.isEnabled()); - setAttributes(mRule); - } - }; - protected void setAttributes(AutomaticZenRule rule) { final boolean isSchedule = ZenModeConfig.isValidScheduleConditionId( rule.getConditionId(), true); @@ -178,6 +126,30 @@ public class ZenRulePreference extends TwoTargetPreference { } private String computeRuleSummary(AutomaticZenRule rule) { + if (rule != null) { + // handle schedule-based rules + ScheduleInfo schedule = + ZenModeConfig.tryParseScheduleConditionId(rule.getConditionId()); + if (schedule != null) { + String desc = mScheduleHelper.getDaysAndTimeSummary(mContext, schedule); + return (desc != null) ? desc : + mContext.getResources().getString( + R.string.zen_mode_schedule_rule_days_none); + } + + // handle event-based rules + ZenModeConfig.EventInfo event = + ZenModeConfig.tryParseEventConditionId(rule.getConditionId()); + if (event != null) { + if (event.calName != null) { + return event.calName; + } else { + return mContext.getResources().getString( + R.string.zen_mode_event_rule_calendar_any); + } + } + } + return (rule == null || !rule.isEnabled()) ? mContext.getResources().getString(R.string.switch_off_text) : mContext.getResources().getString(R.string.switch_on_text); diff --git a/src/com/android/settings/notification/zen/ZenRuleScheduleHelper.java b/src/com/android/settings/notification/zen/ZenRuleScheduleHelper.java new file mode 100644 index 00000000000..9a4f108437a --- /dev/null +++ b/src/com/android/settings/notification/zen/ZenRuleScheduleHelper.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2022 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.zen; + +import android.content.Context; +import android.service.notification.ZenModeConfig.ScheduleInfo; +import android.text.format.DateFormat; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.R; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Locale; + +/** + * Helper class for shared functionality regarding descriptions of custom zen rule schedules. + */ +public class ZenRuleScheduleHelper { + // per-instance to ensure we're always using the current locale + private SimpleDateFormat mDayFormat; + + // Default constructor, which will use the current locale. + public ZenRuleScheduleHelper() { + mDayFormat = new SimpleDateFormat("EEE"); + } + + // Constructor for tests to provide an explicit locale + @VisibleForTesting + public ZenRuleScheduleHelper(Locale locale) { + mDayFormat = new SimpleDateFormat("EEE", locale); + } + + /** + * Returns an ordered, comma-separated list of the days that a schedule applies, or null if no + * days. + */ + public String getDaysDescription(Context context, ScheduleInfo schedule) { + // Compute an ordered, delimited list of day names based on the persisted user config. + final int[] days = schedule.days; + if (days != null && days.length > 0) { + final StringBuilder sb = new StringBuilder(); + final Calendar c = Calendar.getInstance(); + int[] daysOfWeek = ZenModeScheduleDaysSelection.getDaysOfWeekForLocale(c); + for (int i = 0; i < daysOfWeek.length; i++) { + final int day = daysOfWeek[i]; + for (int j = 0; j < days.length; j++) { + if (day == days[j]) { + c.set(Calendar.DAY_OF_WEEK, day); + if (sb.length() > 0) { + sb.append(context.getString(R.string.summary_divider_text)); + } + sb.append(mDayFormat.format(c.getTime())); + break; + } + } + } + + if (sb.length() > 0) { + return sb.toString(); + } + } + return null; + } + + /** + * Returns an ordered summarized list of the days on which this schedule applies, with + * adjacent days grouped together ("Sun-Wed" instead of "Sun,Mon,Tue,Wed"). + */ + public String getShortDaysSummary(Context context, ScheduleInfo schedule) { + // Compute a list of days with contiguous days grouped together, for example: "Sun-Thu" or + // "Sun-Mon,Wed,Fri" + final int[] days = schedule.days; + if (days != null && days.length > 0) { + final StringBuilder sb = new StringBuilder(); + final Calendar cStart = Calendar.getInstance(); + final Calendar cEnd = Calendar.getInstance(); + int[] daysOfWeek = ZenModeScheduleDaysSelection.getDaysOfWeekForLocale(cStart); + // the i for loop goes through days in order as determined by locale. as we walk through + // the days of the week, keep track of "start" and "last seen" as indicators for + // what's contiguous, and initialize them to something not near actual indices + int startDay = Integer.MIN_VALUE; + int lastSeenDay = Integer.MIN_VALUE; + for (int i = 0; i < daysOfWeek.length; i++) { + final int day = daysOfWeek[i]; + + // by default, output if this day is *not* included in the schedule, and thus + // ends a previously existing block. if this day is included in the schedule + // after all (as will be determined in the inner for loop), then output will be set + // to false. + boolean output = (i == lastSeenDay + 1); + for (int j = 0; j < days.length; j++) { + if (day == days[j]) { + // match for this day in the schedule (indicated by counter i) + if (i == lastSeenDay + 1) { + // contiguous to the block we're walking through right now, record it + // (specifically, i, the day index) and move on to the next day + lastSeenDay = i; + output = false; + } else { + // it's a match, but not 1 past the last match, we are starting a new + // block + startDay = i; + lastSeenDay = i; + } + + // if there is a match on the last day, also make sure to output at the end + // of this loop, and mark the day as the last day we'll have seen in the + // scheduled days. + if (i == daysOfWeek.length - 1) { + output = true; + } + break; + } + } + + // output in either of 2 cases: this day is not a match, so has ended any previous + // block, or this day *is* a match but is the last day of the week, so we need to + // summarize + if (output) { + // either describe just the single day if startDay == lastSeenDay, or + // output "startDay - lastSeenDay" as a group + if (sb.length() > 0) { + sb.append(context.getString(R.string.summary_divider_text)); + } + + if (startDay == lastSeenDay) { + // last group was only one day + cStart.set(Calendar.DAY_OF_WEEK, daysOfWeek[startDay]); + sb.append(mDayFormat.format(cStart.getTime())); + } else { + // last group was a contiguous group of days, so group them together + cStart.set(Calendar.DAY_OF_WEEK, daysOfWeek[startDay]); + cEnd.set(Calendar.DAY_OF_WEEK, daysOfWeek[lastSeenDay]); + sb.append(context.getString(R.string.summary_range_symbol_combination, + mDayFormat.format(cStart.getTime()), + mDayFormat.format(cEnd.getTime()))); + } + } + } + + if (sb.length() > 0) { + return sb.toString(); + } + } + return null; + } + + /** + * Convenience method for representing the specified time in string format. + */ + private String timeString(Context context, int hour, int minute) { + final Calendar c = Calendar.getInstance(); + c.set(Calendar.HOUR_OF_DAY, hour); + c.set(Calendar.MINUTE, minute); + return DateFormat.getTimeFormat(context).format(c.getTime()); + } + + /** + * Combination description for a zen rule schedule including both day summary and time bounds. + */ + public String getDaysAndTimeSummary(Context context, ScheduleInfo schedule) { + final StringBuilder sb = new StringBuilder(); + String daysSummary = getShortDaysSummary(context, schedule); + if (daysSummary == null) { + // no use outputting times without dates + return null; + } + sb.append(daysSummary); + sb.append(context.getString(R.string.summary_divider_text)); + sb.append(context.getString(R.string.summary_range_symbol_combination, + timeString(context, schedule.startHour, schedule.startMinute), + timeString(context, schedule.endHour, schedule.endMinute))); + + return sb.toString(); + } +} diff --git a/tests/robotests/src/com/android/settings/inputmethod/AvailableVirtualKeyboardFragmentTest.java b/tests/robotests/src/com/android/settings/inputmethod/AvailableVirtualKeyboardFragmentTest.java new file mode 100644 index 00000000000..c6c6a66b986 --- /dev/null +++ b/tests/robotests/src/com/android/settings/inputmethod/AvailableVirtualKeyboardFragmentTest.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2021 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.inputmethod; + +import static com.android.settings.dashboard.profileselector.ProfileSelectFragment.EXTRA_PROFILE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.os.Bundle; +import android.provider.SearchIndexableResource; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.InputMethodSubtype; + +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.dashboard.profileselector.ProfileSelectFragment; +import com.android.settings.testutils.shadow.ShadowInputMethodManagerWithMethodList; +import com.android.settings.testutils.shadow.ShadowSecureSettings; +import com.android.settingslib.inputmethod.InputMethodPreference; +import com.android.settingslib.inputmethod.InputMethodSettingValuesWrapper; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = { + ShadowSecureSettings.class, + ShadowInputMethodManagerWithMethodList.class +}) +public class AvailableVirtualKeyboardFragmentTest { + + @Mock + private InputMethodManager mInputMethodManager; + @Mock + private InputMethodSettingValuesWrapper mValuesWrapper; + @Mock + private PreferenceScreen mPreferenceScreen; + @Mock + private PreferenceManager mPreferenceManager; + @Mock + private InputMethodPreference mInputMethodPreference; + private Context mContext; + private AvailableVirtualKeyboardFragment mFragment; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + initFragment(); + initMock(); + } + + @Test + public void onCreatePreferences_shouldAddResource() { + mFragment.onAttach(mContext); + + mFragment.onCreatePreferences(new Bundle(), "test"); + + verify(mFragment).addPreferencesFromResource(R.xml.available_virtual_keyboard); + } + + @Test + public void onResume_refreshAllInputMethodAndSubtypes() { + mFragment.onAttach(mContext); + + mFragment.onResume(); + + // One invocation is in onResume(), another is in updateInputMethodPreferenceViews(). + verify(mValuesWrapper, times(2)).refreshAllInputMethodAndSubtypes(); + } + + @Test + public void onResume_updateInputMethodPreferenceViews() { + mFragment.onAttach(mContext); + + mFragment.onResume(); + + verify(mFragment).updateInputMethodPreferenceViews(); + } + + @Test + public void onSaveInputMethodPreference_refreshAllInputMethodAndSubtypes() { + mFragment.onAttach(mContext); + + mFragment.onSaveInputMethodPreference(mInputMethodPreference); + + verify(mValuesWrapper).refreshAllInputMethodAndSubtypes(); + } + + @Test + public void updateInputMethodPreferenceViews_callsExpectedMethods() { + mFragment.onAttach(mContext); + + mFragment.updateInputMethodPreferenceViews(); + + verify(mValuesWrapper).getInputMethodList(); + verify(mInputMethodManager).getEnabledInputMethodListAsUser(anyInt()); + } + + @Test + public void updateInputMethodPreferenceViews_addExpectedInputMethodPreference() { + final int inputMethodNums = 5; + mFragment.onAttach(mContext); + when(mValuesWrapper.getInputMethodList()).thenReturn(createFakeInputMethodInfoList( + "test", inputMethodNums)); + + mFragment.updateInputMethodPreferenceViews(); + + assertThat(mFragment.mInputMethodPreferenceList).hasSize(inputMethodNums); + } + + @Test + public void searchIndexProvider_shouldIndexResource() { + final List indexRes = + AvailableVirtualKeyboardFragment.SEARCH_INDEX_DATA_PROVIDER + .getXmlResourcesToIndex(RuntimeEnvironment.application, true /* enabled */); + + assertThat(indexRes).isNotNull(); + assertThat(indexRes.get(0).xmlResId).isEqualTo(mFragment.getPreferenceScreenResId()); + } + + private void initFragment() { + final Bundle bundle = new Bundle(); + bundle.putInt(EXTRA_PROFILE, ProfileSelectFragment.ProfileType.PERSONAL); + mFragment = spy(new AvailableVirtualKeyboardFragment()); + mFragment.setArguments(bundle); + mFragment.mInputMethodSettingValues = mValuesWrapper; + ReflectionHelpers.setField(mFragment, "mPreferenceManager", mPreferenceManager); + } + + private void initMock() { + when(mFragment.getContext()).thenReturn(mContext); + when(mFragment.getPreferenceScreen()).thenReturn(mPreferenceScreen); + when(mPreferenceManager.getContext()).thenReturn(mContext); + when(mContext.getSystemService(InputMethodManager.class)).thenReturn(mInputMethodManager); + } + + private List createFakeInputMethodInfoList(final String name, int num) { + List subtypes = new ArrayList<>(); + + subtypes.add(new InputMethodSubtype.InputMethodSubtypeBuilder() + .build()); + subtypes.add(new InputMethodSubtype.InputMethodSubtypeBuilder() + .build()); + + final ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.serviceInfo = new ServiceInfo(); + resolveInfo.serviceInfo.packageName = "com.android.ime"; + resolveInfo.serviceInfo.name = name; + resolveInfo.serviceInfo.applicationInfo = new ApplicationInfo(); + resolveInfo.serviceInfo.applicationInfo.enabled = true; + + List inputMethodInfoList = new ArrayList<>(); + for (int i = 0; i < num; i++) { + inputMethodInfoList.add(new InputMethodInfo( + resolveInfo, + false /* isAuxIme */, + "TestSettingsActivity", + subtypes, + 0 /* isDefaultResId */, + true /* forceDefault */)); + } + return inputMethodInfoList; + } +} diff --git a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java index 65be91f71f4..df38e7f72a0 100644 --- a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java +++ b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java @@ -246,7 +246,6 @@ public class SettingsSliceProviderTest { } @Test - @Ignore @Config(shadows = ShadowStrictMode.class) public void onBindSlice_backgroundThread_shouldOverrideStrictMode() { ShadowThreadUtils.setIsMainThread(false); diff --git a/tests/robotests/src/com/android/settings/testutils/FakeToggleController.java b/tests/robotests/src/com/android/settings/testutils/FakeToggleController.java index 4e968a29c70..9ace6bb0cf0 100644 --- a/tests/robotests/src/com/android/settings/testutils/FakeToggleController.java +++ b/tests/robotests/src/com/android/settings/testutils/FakeToggleController.java @@ -21,13 +21,14 @@ import android.net.Uri; import android.net.wifi.WifiManager; import android.provider.Settings; +import com.android.settings.R; import com.android.settings.core.TogglePreferenceController; import com.android.settings.slices.SliceBackgroundWorker; public class FakeToggleController extends TogglePreferenceController { public static final String AVAILABILITY_KEY = "fake_toggle_availability_key"; - public static final int HIGHLIGHT_MENU_RES = 5678; + public static final int HIGHLIGHT_MENU_RES = R.string.menu_key_about_device; public static final IntentFilter INTENT_FILTER = new IntentFilter( WifiManager.WIFI_AP_STATE_CHANGED_ACTION); diff --git a/tests/unit/src/com/android/settings/notification/zen/ZenRuleScheduleHelperTest.java b/tests/unit/src/com/android/settings/notification/zen/ZenRuleScheduleHelperTest.java new file mode 100644 index 00000000000..2faee080c6e --- /dev/null +++ b/tests/unit/src/com/android/settings/notification/zen/ZenRuleScheduleHelperTest.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2022 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.zen; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.os.LocaleList; +import android.service.notification.ZenModeConfig.ScheduleInfo; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Calendar; +import java.util.Locale; + +@RunWith(AndroidJUnit4.class) +public class ZenRuleScheduleHelperTest { + private ZenRuleScheduleHelper mScheduleHelper; + private ScheduleInfo mScheduleInfo; + + private Context mContext; + + @Mock + private Resources mResources; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + + // explicitly initialize to Locale.US just for ease of explicitly testing the string values + // of the days of the week if the test locale doesn't happen to be in the US + mScheduleHelper = new ZenRuleScheduleHelper(Locale.US); + mScheduleInfo = new ScheduleInfo(); + + mContext = spy(ApplicationProvider.getApplicationContext()); + when(mContext.getResources()).thenReturn(mResources); + + // Resources will be called upon to join strings together, either to get a divider + // or a combination of two strings. Conveniently, these have different signatures. + // Divider method calls getString(string divider id) + when(mResources.getString(anyInt())).thenReturn(","); + + // Combination method calls getString(combination id, first item, second item) + // and returns "first - second" + when(mResources.getString(anyInt(), anyString(), anyString())).thenAnswer( + invocation -> { + return invocation.getArgument(1).toString() // first item + + "-" + + invocation.getArgument(2).toString(); // second item + }); + + // for locale used in time format + Configuration config = new Configuration(); + config.setLocales(new LocaleList(Locale.US)); + when(mResources.getConfiguration()).thenReturn(config); + } + + @Test + public void getDaysDescription() { + // Test various cases of where the days are set. + // No days + mScheduleInfo.days = new int[] {}; + assertThat(mScheduleHelper.getDaysDescription(mContext, mScheduleInfo)).isNull(); + + // one day + mScheduleInfo.days = new int[] {Calendar.FRIDAY}; + assertThat(mScheduleHelper.getDaysDescription(mContext, mScheduleInfo)).isEqualTo("Fri"); + + // Monday through Friday + mScheduleInfo.days = new int[] {Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY, + Calendar.THURSDAY, Calendar.FRIDAY}; + assertThat(mScheduleHelper.getDaysDescription(mContext, mScheduleInfo)) + .isEqualTo("Mon,Tue,Wed,Thu,Fri"); + + // Some scattered days of the week + mScheduleInfo.days = new int[] {Calendar.SUNDAY, Calendar.WEDNESDAY, Calendar.THURSDAY, + Calendar.SATURDAY}; + assertThat(mScheduleHelper.getDaysDescription(mContext, mScheduleInfo)) + .isEqualTo("Sun,Wed,Thu,Sat"); + } + + @Test + public void getShortDaysSummary_noOrSingleDays() { + // Test various cases for grouping and not-grouping of days. + // No days + mScheduleInfo.days = new int[]{}; + assertThat(mScheduleHelper.getShortDaysSummary(mContext, mScheduleInfo)).isNull(); + + // A single day at the beginning of the week + mScheduleInfo.days = new int[]{Calendar.SUNDAY}; + assertThat(mScheduleHelper.getShortDaysSummary(mContext, mScheduleInfo)).isEqualTo("Sun"); + + // A single day in the middle of the week + mScheduleInfo.days = new int[]{Calendar.THURSDAY}; + assertThat(mScheduleHelper.getShortDaysSummary(mContext, mScheduleInfo)).isEqualTo("Thu"); + + // A single day at the end of the week + mScheduleInfo.days = new int[]{Calendar.SATURDAY}; + assertThat(mScheduleHelper.getShortDaysSummary(mContext, mScheduleInfo)).isEqualTo("Sat"); + } + + @Test + public void getShortDaysSummary_oneGroup() { + // The whole week + mScheduleInfo.days = new int[] {Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY, + Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY}; + assertThat(mScheduleHelper.getShortDaysSummary(mContext, mScheduleInfo)) + .isEqualTo("Sun-Sat"); + + // Various cases of one big group + // Sunday through Thursday + mScheduleInfo.days = new int[] {Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY, + Calendar.WEDNESDAY, Calendar.THURSDAY}; + assertThat(mScheduleHelper.getShortDaysSummary(mContext, mScheduleInfo)) + .isEqualTo("Sun-Thu"); + + // Wednesday through Saturday + mScheduleInfo.days = new int[] {Calendar.WEDNESDAY, Calendar.THURSDAY, + Calendar.FRIDAY, Calendar.SATURDAY}; + assertThat(mScheduleHelper.getShortDaysSummary(mContext, mScheduleInfo)) + .isEqualTo("Wed-Sat"); + + // Monday through Friday + mScheduleInfo.days = new int[] {Calendar.MONDAY, Calendar.TUESDAY, + Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY}; + assertThat(mScheduleHelper.getShortDaysSummary(mContext, mScheduleInfo)) + .isEqualTo("Mon-Fri"); + } + + @Test + public void getShortDaysSummary_mixed() { + // cases combining groups and single days scattered around + mScheduleInfo.days = new int[] {Calendar.SUNDAY, Calendar.TUESDAY, + Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.SATURDAY}; + assertThat(mScheduleHelper.getShortDaysSummary(mContext, mScheduleInfo)) + .isEqualTo("Sun,Tue-Thu,Sat"); + + mScheduleInfo.days = new int[] {Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY, + Calendar.WEDNESDAY, Calendar.FRIDAY, Calendar.SATURDAY}; + assertThat(mScheduleHelper.getShortDaysSummary(mContext, mScheduleInfo)) + .isEqualTo("Sun-Wed,Fri-Sat"); + + mScheduleInfo.days = new int[] {Calendar.MONDAY, Calendar.WEDNESDAY, + Calendar.FRIDAY, Calendar.SATURDAY}; + assertThat(mScheduleHelper.getShortDaysSummary(mContext, mScheduleInfo)) + .isEqualTo("Mon,Wed,Fri-Sat"); + } + + @Test + public void getDaysAndTimeSummary() { + // Combination days & time settings + // No days, no output, even if the times are set. + mScheduleInfo.startHour = 10; + mScheduleInfo.endHour = 16; + mScheduleInfo.days = new int[]{}; + assertThat(mScheduleHelper.getDaysAndTimeSummary(mContext, mScheduleInfo)).isNull(); + + // If there are days then they are combined with the time combination + mScheduleInfo.days = new int[]{Calendar.SUNDAY, Calendar.MONDAY, Calendar.WEDNESDAY}; + assertThat(mScheduleHelper.getDaysAndTimeSummary(mContext, mScheduleInfo)) + .isEqualTo("Sun-Mon,Wed,10:00 AM-4:00 PM"); + } +} diff --git a/tests/unit/src/com/android/settings/testutils/FakeToggleController.java b/tests/unit/src/com/android/settings/testutils/FakeToggleController.java index c232479d61c..a7e7320a96d 100644 --- a/tests/unit/src/com/android/settings/testutils/FakeToggleController.java +++ b/tests/unit/src/com/android/settings/testutils/FakeToggleController.java @@ -21,13 +21,14 @@ import android.net.Uri; import android.net.wifi.WifiManager; import android.provider.Settings; +import com.android.settings.R; import com.android.settings.core.TogglePreferenceController; import com.android.settings.slices.SliceBackgroundWorker; public class FakeToggleController extends TogglePreferenceController { public static final String AVAILABILITY_KEY = "fake_toggle_availability_key"; - public static final int HIGHLIGHT_MENU_RES = 5678; + public static final int HIGHLIGHT_MENU_RES = R.string.menu_key_about_device; public static final IntentFilter INTENT_FILTER = new IntentFilter( WifiManager.WIFI_AP_STATE_CHANGED_ACTION);