diff --git a/res/values/arrays.xml b/res/values/arrays.xml index adaea1bd921..4680936b99b 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -1552,4 +1552,8 @@ Theater Flower + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index d610e1fcf3a..96dc07ebbaa 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -8076,6 +8076,9 @@ %1$d hr, %2$d min + + Turn on automatically + Schedule @@ -12198,10 +12201,17 @@ Settings Panel - - Force desktop mode - - Force experimental desktop mode on secondary displays + + Enable freeform windows + + Enable support for freeform windows. + + + + Enable freeform windowing on second display + + Enable freeform windows only on secondary display. + Enable non-resizable in multi window @@ -13165,12 +13175,12 @@ - A reboot is required to enable freeform - support. - - A reboot is required to force desktop mode on - secondary displays. + A reboot is required to enable legacy freeform windowing support. + + A reboot is required to change freeform windowing support. + + A reboot is required to force freeform windowing on secondary displays. Reboot now diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml index 866a529dd8b..9420f59e881 100644 --- a/res/xml/development_settings.xml +++ b/res/xml/development_settings.xml @@ -737,6 +737,11 @@ android:title="@string/force_resizable_activities" android:summary="@string/force_resizable_activities_summary" /> + + false; + case SETTING_VALUE_ON -> true; + case SETTING_VALUE_UNSET -> shouldDevOptionBeEnabledByDefault; + default -> { + Log.w(TAG, "Invalid override for desktop mode: " + mode); + yield shouldDevOptionBeEnabledByDefault; + } + }; + ((TwoStatePreference) mPreference).setChecked(shouldDevOptionBeEnabled); } @Override protected void onDeveloperOptionsSwitchDisabled() { super.onDeveloperOptionsSwitchDisabled(); Settings.Global.putInt(mContext.getContentResolver(), - DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, SETTING_VALUE_OFF); - ((TwoStatePreference) mPreference).setChecked(false); + DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, SETTING_VALUE_UNSET); } - @VisibleForTesting - String getBuildType() { - return Build.TYPE; + private boolean isDeviceEligibleForDesktopMode() { + boolean enforceDeviceRestrictions = SystemProperties.getBoolean( + "persist.wm.debug.desktop_mode_enforce_device_restrictions", true); + boolean isDesktopModeSupported = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_isDesktopModeSupported); + return !enforceDeviceRestrictions || isDesktopModeSupported; } } diff --git a/src/com/android/settings/development/DesktopModeSecondaryDisplayPreferenceController.java b/src/com/android/settings/development/DesktopModeSecondaryDisplayPreferenceController.java new file mode 100644 index 00000000000..ff513c249f5 --- /dev/null +++ b/src/com/android/settings/development/DesktopModeSecondaryDisplayPreferenceController.java @@ -0,0 +1,96 @@ +/* + * 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.development; + +import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS; + +import android.content.Context; +import android.os.Build; +import android.provider.Settings; + +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; +import androidx.preference.TwoStatePreference; + +import com.android.settings.R; +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.development.DeveloperOptionsPreferenceController; + +/** + * Preference controller to control Desktop mode features on secondary display + */ +public class DesktopModeSecondaryDisplayPreferenceController extends + DeveloperOptionsPreferenceController + implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin, + RebootConfirmationDialogHost { + + private static final String ENABLE_DESKTOP_MODE_ON_SECONDARY_DISPLAY = + "force_desktop_mode_on_external_displays"; + + @VisibleForTesting + static final int SETTING_VALUE_OFF = 0; + @VisibleForTesting + static final int SETTING_VALUE_ON = 1; + + @Nullable + private final DevelopmentSettingsDashboardFragment mFragment; + + public DesktopModeSecondaryDisplayPreferenceController( + Context context, @Nullable DevelopmentSettingsDashboardFragment fragment) { + super(context); + mFragment = fragment; + } + + @Override + public String getPreferenceKey() { + return ENABLE_DESKTOP_MODE_ON_SECONDARY_DISPLAY; + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + final boolean isEnabled = (Boolean) newValue; + Settings.Global.putInt(mContext.getContentResolver(), + DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, + isEnabled ? SETTING_VALUE_ON : SETTING_VALUE_OFF); + if (isEnabled && mFragment != null) { + RebootConfirmationDialogFragment.show( + mFragment, R.string.reboot_dialog_force_desktop_mode, this); + } + return true; + } + + @Override + public void updateState(Preference preference) { + final int mode = Settings.Global.getInt(mContext.getContentResolver(), + DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, SETTING_VALUE_OFF); + ((TwoStatePreference) mPreference).setChecked(mode != SETTING_VALUE_OFF); + } + + @Override + protected void onDeveloperOptionsSwitchDisabled() { + super.onDeveloperOptionsSwitchDisabled(); + Settings.Global.putInt(mContext.getContentResolver(), + DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, SETTING_VALUE_OFF); + ((TwoStatePreference) mPreference).setChecked(false); + } + + @VisibleForTesting + String getBuildType() { + return Build.TYPE; + } +} diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java index 38cb6c72e96..0389b4521fb 100644 --- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java +++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java @@ -749,6 +749,7 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra controllers.add(new ResizableActivityPreferenceController(context)); controllers.add(new FreeformWindowsPreferenceController(context, fragment)); controllers.add(new DesktopModePreferenceController(context, fragment)); + controllers.add(new DesktopModeSecondaryDisplayPreferenceController(context, fragment)); controllers.add(new NonResizableMultiWindowPreferenceController(context)); controllers.add(new ShortcutManagerThrottlingPreferenceController(context)); controllers.add(new EnableGnssRawMeasFullTrackingPreferenceController(context)); diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java index 98e1a6e8470..e12cc9ff01d 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java +++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java @@ -26,6 +26,7 @@ import android.util.SparseIntArray; import androidx.annotation.NonNull; import com.android.settings.fuelgauge.batteryusage.BatteryDiffData; +import com.android.settings.fuelgauge.batteryusage.BatteryEvent; import com.android.settings.fuelgauge.batteryusage.DetectRequestSourceType; import com.android.settings.fuelgauge.batteryusage.PowerAnomalyEventList; import com.android.settingslib.fuelgauge.Estimate; @@ -43,6 +44,9 @@ public interface PowerUsageFeatureProvider { /** Check whether the battery tips card is enabled in the battery usage page */ boolean isBatteryTipsEnabled(); + /** Check whether overwrite the app optimization mode to restricted mode is enabled */ + boolean isRestrictedModeOverwriteEnabled(); + /** Check whether force expire the app optimization mode. */ boolean isForceExpireAppOptimizationModeEnabled(); @@ -166,5 +170,6 @@ public interface PowerUsageFeatureProvider { boolean processBatteryReattributeData( @NonNull Context context, @NonNull Map batteryDiffDataMap, + @NonNull List batteryEventList, final boolean isFromPeriodJob); } diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java index 8ba63749cbd..4f163a28f47 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java +++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java @@ -18,7 +18,6 @@ package com.android.settings.fuelgauge; import static com.android.settings.Utils.SYSTEMUI_PACKAGE_NAME; -import android.annotation.Nullable; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -28,9 +27,11 @@ import android.util.ArraySet; import android.util.SparseIntArray; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.internal.util.ArrayUtils; import com.android.settings.fuelgauge.batteryusage.BatteryDiffData; +import com.android.settings.fuelgauge.batteryusage.BatteryEvent; import com.android.settings.fuelgauge.batteryusage.DetectRequestSourceType; import com.android.settings.fuelgauge.batteryusage.PowerAnomalyEventList; import com.android.settingslib.fuelgauge.Estimate; @@ -87,6 +88,11 @@ public class PowerUsageFeatureProviderImpl implements PowerUsageFeatureProvider return false; } + @Override + public boolean isRestrictedModeOverwriteEnabled() { + return false; + } + @Override public boolean isForceExpireAppOptimizationModeEnabled() { return false; @@ -254,6 +260,7 @@ public class PowerUsageFeatureProviderImpl implements PowerUsageFeatureProvider public boolean processBatteryReattributeData( @NonNull Context context, @NonNull Map batteryDiffDataMap, + @NonNull List batteryEventList, final boolean isFromPeriodJob) { return false; } diff --git a/src/com/android/settings/fuelgauge/batteryusage/AppOptModeSharedPreferencesUtils.kt b/src/com/android/settings/fuelgauge/batteryusage/AppOptModeSharedPreferencesUtils.kt index afff1c9a1bb..508c4df4da3 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/AppOptModeSharedPreferencesUtils.kt +++ b/src/com/android/settings/fuelgauge/batteryusage/AppOptModeSharedPreferencesUtils.kt @@ -76,8 +76,7 @@ object AppOptModeSharedPreferencesUtils { fun resetExpiredAppOptModeBeforeTimestamp(context: Context, queryTimestampMs: Long) = synchronized(appOptimizationModeLock) { val forceExpireEnabled = - featureFactory - .powerUsageFeatureProvider.isForceExpireAppOptimizationModeEnabled + featureFactory.powerUsageFeatureProvider.isForceExpireAppOptimizationModeEnabled val eventsMap = getAppOptModeEventsMap(context) val expirationUids = ArrayList(eventsMap.size) for ((uid, event) in eventsMap) { @@ -113,12 +112,22 @@ object AppOptModeSharedPreferencesUtils { getBatteryOptimizeUtils: (Int, String) -> BatteryOptimizeUtils, ) = synchronized(appOptimizationModeLock) { + val restrictedModeOverwriteEnabled = + featureFactory.powerUsageFeatureProvider.isRestrictedModeOverwriteEnabled val eventsMap = getAppOptModeEventsMap(context) val expirationEvents: MutableMap = ArrayMap() for (i in uids.indices) { val uid = uids[i] val packageName = packageNames[i] val optimizationMode = optimizationModes[i] + if ( + !restrictedModeOverwriteEnabled && + optimizationMode == BatteryOptimizeUtils.MODE_RESTRICTED + ) { + // Unable to set restricted mode due to flag protection. + Log.w(TAG, "setOptimizationMode($packageName) into restricted ignored") + continue + } val originalOptMode: Int = updateBatteryOptimizationMode( context, diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java index 2b88d34a17a..0a1a547ecc2 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java +++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java @@ -522,7 +522,7 @@ public class DataProcessManager { FeatureFactory.getFeatureFactory() .getPowerUsageFeatureProvider(); featureProvider.processBatteryReattributeData( - mContext, batteryDiffDataMap, mIsFromPeriodJob); + mContext, batteryDiffDataMap, mBatteryEventList, mIsFromPeriodJob); Log.d( TAG, diff --git a/src/com/android/settings/notification/modes/AbstractZenModePreferenceController.java b/src/com/android/settings/notification/modes/AbstractZenModePreferenceController.java index 9f819d1b6e4..a4bd2aac5f6 100644 --- a/src/com/android/settings/notification/modes/AbstractZenModePreferenceController.java +++ b/src/com/android/settings/notification/modes/AbstractZenModePreferenceController.java @@ -24,6 +24,7 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.notification.modes.ZenMode; @@ -91,6 +92,16 @@ abstract class AbstractZenModePreferenceController extends AbstractPreferenceCon updateState(preference); } + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + if (mZenMode != null) { + displayPreference(screen, mZenMode); + } + } + + public void displayPreference(PreferenceScreen screen, @NonNull ZenMode zenMode) {} + @Override public final void updateState(Preference preference) { super.updateState(preference); diff --git a/src/com/android/settings/notification/modes/ZenModeFragment.java b/src/com/android/settings/notification/modes/ZenModeFragment.java index 67815b10088..3a64fb2f1a0 100644 --- a/src/com/android/settings/notification/modes/ZenModeFragment.java +++ b/src/com/android/settings/notification/modes/ZenModeFragment.java @@ -35,6 +35,7 @@ import java.util.ArrayList; import java.util.List; public class ZenModeFragment extends ZenModeFragmentBase { + // for mode deletion menu private static final int DELETE_MODE = 1; @@ -61,7 +62,8 @@ public class ZenModeFragment extends ZenModeFragmentBase { prefControllers.add(new ZenModeDisplayLinkPreferenceController( context, "mode_display_settings", mBackend, mHelperBackend)); prefControllers.add(new ZenModeSetTriggerLinkPreferenceController(context, - "zen_automatic_trigger_category", this, mBackend)); + "zen_automatic_trigger_category", this, mBackend, + context.getPackageManager())); prefControllers.add(new InterruptionFilterPreferenceController( context, "allow_filtering", mBackend)); prefControllers.add(new ManualDurationPreferenceController( diff --git a/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java index 1c96fee7d0b..7328d918941 100644 --- a/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java @@ -18,34 +18,62 @@ package com.android.settings.notification.modes; import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR; import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME; +import static android.app.NotificationManager.EXTRA_AUTOMATIC_RULE_ID; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ComponentInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.service.notification.ConditionProviderService; +import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.utils.ManagedServiceSettings; +import com.android.settings.utils.ZenServiceListing; import com.android.settingslib.PrimarySwitchPreference; import com.android.settingslib.notification.modes.ZenMode; import com.android.settingslib.notification.modes.ZenModesBackend; +import java.util.List; + /** * Preference controller for the link to an individual mode's configuration page. */ class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenceController { + private static final String TAG = "ZenModeSetTriggerLink"; + @VisibleForTesting protected static final String AUTOMATIC_TRIGGER_PREF_KEY = "zen_automatic_trigger_settings"; + private static final ManagedServiceSettings.Config CONFIG = + ZenModesListFragment.getConditionProviderConfig(); + + private ZenServiceListing mServiceListing; + private final PackageManager mPm; private final DashboardFragment mFragment; ZenModeSetTriggerLinkPreferenceController(Context context, String key, - DashboardFragment fragment, - ZenModesBackend backend) { + DashboardFragment fragment, ZenModesBackend backend, + PackageManager packageManager) { super(context, key, backend); mFragment = fragment; + mPm = packageManager; + } + + @VisibleForTesting + protected void setServiceListing(ZenServiceListing serviceListing) { + mServiceListing = serviceListing; } @Override @@ -53,6 +81,15 @@ class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenc return !zenMode.isManualDnd(); } + @Override + public void displayPreference(PreferenceScreen screen, @NonNull ZenMode zenMode) { + if (mServiceListing == null) { + mServiceListing = new ZenServiceListing( + mContext, CONFIG, zenMode.getRule().getPackageName()); + } + mServiceListing.reloadApprovedServices(); + } + @Override public void updateState(Preference preference, @NonNull ZenMode zenMode) { // This controller is expected to govern a preference category so that it controls the @@ -70,29 +107,39 @@ class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenc switchPref.setOnPreferenceClickListener(null); switchPref.setIntent(null); - if (zenMode.isSystemOwned() && zenMode.getType() == TYPE_SCHEDULE_TIME) { - switchPref.setTitle(R.string.zen_mode_set_schedule_link); - // TODO: b/332937635 - set correct metrics category - switchPref.setIntent(ZenSubSettingLauncher.forModeFragment(mContext, - ZenModeSetScheduleFragment.class, zenMode.getId(), 0).toIntent()); - } else if (zenMode.isSystemOwned() && zenMode.getType() == TYPE_SCHEDULE_CALENDAR) { - switchPref.setTitle(R.string.zen_mode_set_calendar_link); - switchPref.setIcon(null); - // TODO: b/332937635 - set correct metrics category - switchPref.setIntent(ZenSubSettingLauncher.forModeFragment(mContext, - ZenModeSetCalendarFragment.class, zenMode.getId(), 0).toIntent()); - } else if (zenMode.isSystemOwned()) { - switchPref.setTitle(R.string.zen_mode_select_schedule); - switchPref.setIcon(R.drawable.ic_add_24dp); - switchPref.setSummary(""); - // TODO: b/342156843 - Hide the switch (needs support in SettingsLib). - switchPref.setOnPreferenceClickListener(clickedPreference -> { - ZenModeScheduleChooserDialog.show(mFragment, mOnScheduleOptionListener); - return true; - }); + if (zenMode.isSystemOwned()) { + if (zenMode.getType() == TYPE_SCHEDULE_TIME) { + switchPref.setTitle(R.string.zen_mode_set_schedule_link); + // TODO: b/332937635 - set correct metrics category + switchPref.setIntent(ZenSubSettingLauncher.forModeFragment(mContext, + ZenModeSetScheduleFragment.class, zenMode.getId(), 0).toIntent()); + } else if (zenMode.getType() == TYPE_SCHEDULE_CALENDAR) { + switchPref.setTitle(R.string.zen_mode_set_calendar_link); + switchPref.setIcon(null); + // TODO: b/332937635 - set correct metrics category + switchPref.setIntent(ZenSubSettingLauncher.forModeFragment(mContext, + ZenModeSetCalendarFragment.class, zenMode.getId(), 0).toIntent()); + } else { + switchPref.setTitle(R.string.zen_mode_select_schedule); + switchPref.setIcon(R.drawable.ic_add_24dp); + switchPref.setSummary(""); + // TODO: b/342156843 - Hide the switch (needs support in SettingsLib). + switchPref.setOnPreferenceClickListener(clickedPreference -> { + ZenModeScheduleChooserDialog.show(mFragment, mOnScheduleOptionListener); + return true; + }); + } } else { - // TODO: b/341961712 - direct preference to app-owned intent if available - switchPref.setTitle("not implemented"); + Intent intent = getAppRuleIntent(zenMode); + if (intent != null && isValidIntent(intent)) { + preference.setVisible(true); + switchPref.setTitle(R.string.zen_mode_configuration_link_title); + switchPref.setSummary(zenMode.getRule().getTriggerDescription()); + switchPref.setIntent(intent); + } else { + Log.i(TAG, "No intent found for " + zenMode.getRule().getName()); + preference.setVisible(false); + } } } @@ -114,4 +161,68 @@ class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenc }); // TODO: b/342156843 - Do we want to jump to the corresponding schedule editing screen? }; + + @VisibleForTesting + protected @Nullable Intent getAppRuleIntent(ZenMode zenMode) { + Intent intent = new Intent().addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + .putExtra(ConditionProviderService.EXTRA_RULE_ID, zenMode.getId()) + .putExtra(EXTRA_AUTOMATIC_RULE_ID, zenMode.getId()); + String owner = zenMode.getRule().getPackageName(); + ComponentName configActivity = null; + if (zenMode.getRule().getConfigurationActivity() != null) { + // If a configuration activity is present, use that directly in the intent + configActivity = zenMode.getRule().getConfigurationActivity(); + } else { + // Otherwise, look for a condition provider service for the rule's package + ComponentInfo ci = mServiceListing.findService(zenMode.getRule().getOwner()); + if (ci == null) { + // do nothing + } else if (ci instanceof ActivityInfo) { + // new activity backed rule + intent.setComponent(new ComponentName(ci.packageName, ci.name)); + return intent; + } else if (ci.metaData != null) { + // old service backed rule + final String configurationActivity = ci.metaData.getString( + ConditionProviderService.META_DATA_CONFIGURATION_ACTIVITY); + if (configurationActivity != null) { + configActivity = ComponentName.unflattenFromString(configurationActivity); + } + } + } + + if (configActivity != null) { + // verify that the owner of the rule owns the configuration activity, but only if + // owner exists + intent.setComponent(configActivity); + if (owner == null) { + return intent; + } + try { + int ownerUid = mPm.getPackageUid(owner, 0); + int configActivityOwnerUid = mPm.getPackageUid(configActivity.getPackageName(), 0); + if (ownerUid == configActivityOwnerUid) { + return intent; + } else { + Log.w(TAG, "Config activity not in owner package for " + + zenMode.getRule().getName()); + return null; + } + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Failed to find config activity"); + return null; + } + } + return null; + } + + private boolean isValidIntent(Intent intent) { + List results = mPm.queryIntentActivities( + intent, PackageManager.ResolveInfoFlags.of(0)); + if (intent.resolveActivity(mPm) == null || results.size() == 0) { + Log.w(TAG, "intent for zen rule invalid: " + intent); + return false; + } + return true; + } } diff --git a/src/com/android/settings/notification/modes/ZenModesListFragment.java b/src/com/android/settings/notification/modes/ZenModesListFragment.java index 77107f89d02..1883945944d 100644 --- a/src/com/android/settings/notification/modes/ZenModesListFragment.java +++ b/src/com/android/settings/notification/modes/ZenModesListFragment.java @@ -78,7 +78,7 @@ public class ZenModesListFragment extends ZenModesFragmentBase { return SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION; } - private static ManagedServiceSettings.Config getConditionProviderConfig() { + static ManagedServiceSettings.Config getConditionProviderConfig() { return new ManagedServiceSettings.Config.Builder() .setTag(TAG) .setIntentAction(ConditionProviderService.SERVICE_INTERFACE) diff --git a/src/com/android/settings/spa/app/specialaccess/DisplayOverOtherApps.kt b/src/com/android/settings/spa/app/specialaccess/DisplayOverOtherApps.kt index 904f0afd3d6..55e842fbc06 100644 --- a/src/com/android/settings/spa/app/specialaccess/DisplayOverOtherApps.kt +++ b/src/com/android/settings/spa/app/specialaccess/DisplayOverOtherApps.kt @@ -44,6 +44,16 @@ class DisplayOverOtherAppsListModel(context: Context) : AppOpPermissionListModel logPermissionChange(newAllowed) } + // TODO (b/349195999) + override fun isChangeable(record: AppOpPermissionRecord): Boolean { + if (record.app.packageName in + context.resources.getStringArray(R.array.display_over_apps_permission_change_exempt) + && record.app.isSystemApp()) { + return false + } + return super.isChangeable(record) + } + private fun logPermissionChange(newAllowed: Boolean) { val category = when { newAllowed -> SettingsEnums.APP_SPECIAL_PERMISSION_APPDRAW_ALLOW diff --git a/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt b/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt index 05a8f6a27de..873a2c3083d 100644 --- a/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt +++ b/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt @@ -279,14 +279,14 @@ fun PrimarySimImpl( ) { CreatePrimarySimListPreference( stringResource(id = R.string.primary_sim_calls_title), - primarySimInfo.callsAndSmsList, + primarySimInfo.callsList, callsSelectedId, ImageVector.vectorResource(R.drawable.ic_phone), actionSetCalls ) CreatePrimarySimListPreference( stringResource(id = R.string.primary_sim_texts_title), - primarySimInfo.callsAndSmsList, + primarySimInfo.smsList, textsSelectedId, Icons.AutoMirrored.Outlined.Message, actionSetTexts diff --git a/src/com/android/settings/spa/network/PrimarySimRepository.kt b/src/com/android/settings/spa/network/PrimarySimRepository.kt index e34a29eb0ea..420f8703918 100644 --- a/src/com/android/settings/spa/network/PrimarySimRepository.kt +++ b/src/com/android/settings/spa/network/PrimarySimRepository.kt @@ -22,12 +22,14 @@ import android.telephony.SubscriptionManager import android.util.Log import com.android.settings.R import com.android.settings.network.SubscriptionUtil +import com.android.settings.network.telephony.CarrierConfigRepository import com.android.settingslib.spa.widget.preference.ListPreferenceOption class PrimarySimRepository(private val context: Context) { data class PrimarySimInfo( - val callsAndSmsList: List, + val callsList: List, + val smsList: List, val dataList: List, ) @@ -37,7 +39,8 @@ class PrimarySimRepository(private val context: Context) { return null } - val callsAndSmsList = mutableListOf() + val callsList = mutableListOf() + val smsList = mutableListOf() val dataList = mutableListOf() for (info in selectableSubscriptionInfoList) { val item = ListPreferenceOption( @@ -45,15 +48,22 @@ class PrimarySimRepository(private val context: Context) { text = "${info.displayName}", summary = SubscriptionUtil.getBidiFormattedPhoneNumber(context, info) ?: "", ) - callsAndSmsList += item + callsList += item + smsList += item dataList += item } - callsAndSmsList += ListPreferenceOption( + + val askEveryTime = ListPreferenceOption( id = SubscriptionManager.INVALID_SUBSCRIPTION_ID, text = context.getString(R.string.sim_calls_ask_first_prefs_title), ) + callsList += askEveryTime + if (context.resources + .getBoolean(com.android.internal.R.bool.config_sms_ask_every_time_support)) { + smsList += askEveryTime + } - return PrimarySimInfo(callsAndSmsList, dataList) + return PrimarySimInfo(callsList, smsList, dataList) } private companion object { diff --git a/src/com/android/settings/utils/ZenServiceListing.java b/src/com/android/settings/utils/ZenServiceListing.java index 99f56f621c8..96d700bd1e7 100644 --- a/src/com/android/settings/utils/ZenServiceListing.java +++ b/src/com/android/settings/utils/ZenServiceListing.java @@ -28,11 +28,14 @@ import android.content.pm.ServiceInfo; import android.util.ArraySet; import android.util.Slog; +import androidx.annotation.Nullable; + import java.util.ArrayList; import java.util.List; import java.util.Set; public class ZenServiceListing { + private static final String TAG = "ZenServiceListing"; private final Context mContext; private final ManagedServiceSettings.Config mConfig; @@ -40,9 +43,18 @@ public class ZenServiceListing { private final List mZenCallbacks = new ArrayList<>(); private final NotificationManager mNm; + // only used when android.app.modes_ui flag is true + @Nullable + private String mPkg = null; + public ZenServiceListing(Context context, ManagedServiceSettings.Config config) { + this(context, config, null); + } + + public ZenServiceListing(Context context, ManagedServiceSettings.Config config, @Nullable String pkg) { mContext = context; mConfig = config; + mPkg = pkg; mNm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); } @@ -89,12 +101,16 @@ public class ZenServiceListing { } } - private static void getServices(ManagedServiceSettings.Config c, List list, + private void getServices(ManagedServiceSettings.Config c, List list, PackageManager pm) { final int user = ActivityManager.getCurrentUser(); + Intent queryIntent = new Intent(c.intentAction); + if (mPkg != null) { + queryIntent.setPackage(mPkg); + } List installedServices = pm.queryIntentServicesAsUser( - new Intent(c.intentAction), + queryIntent, PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, user); @@ -115,12 +131,16 @@ public class ZenServiceListing { } } - private static void getActivities(ManagedServiceSettings.Config c, List list, + private void getActivities(ManagedServiceSettings.Config c, List list, PackageManager pm) { final int user = ActivityManager.getCurrentUser(); + Intent queryIntent = new Intent(c.configIntentAction); + if (mPkg != null) { + queryIntent.setPackage(mPkg); + } List resolveInfos = pm.queryIntentActivitiesAsUser( - new Intent(c.configIntentAction), + queryIntent, PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA, user); diff --git a/tests/robotests/src/com/android/settings/development/DesktopModePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/DesktopModePreferenceControllerTest.java index 460f6f9bc08..c74512a502a 100644 --- a/tests/robotests/src/com/android/settings/development/DesktopModePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/development/DesktopModePreferenceControllerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2024 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. @@ -16,10 +16,11 @@ package com.android.settings.development; -import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS; +import static android.provider.Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES; import static com.android.settings.development.DesktopModePreferenceController.SETTING_VALUE_OFF; import static com.android.settings.development.DesktopModePreferenceController.SETTING_VALUE_ON; +import static com.android.settings.development.DesktopModePreferenceController.SETTING_VALUE_UNSET; import static com.google.common.truth.Truth.assertThat; @@ -30,6 +31,10 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.res.Resources; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import androidx.fragment.app.FragmentActivity; @@ -37,24 +42,30 @@ import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; import androidx.preference.PreferenceScreen; import androidx.preference.SwitchPreference; +import androidx.test.core.app.ApplicationProvider; + +import com.android.internal.R; +import com.android.window.flags.Flags; import org.junit.Before; +import org.junit.Rule; 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.shadows.ShadowSystemProperties; @RunWith(RobolectricTestRunner.class) @Config(shadows = { com.android.settings.testutils.shadow.ShadowFragment.class, }) +@EnableFlags(Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) public class DesktopModePreferenceControllerTest { - private static final String ENG_BUILD_TYPE = "eng"; - private static final String USER_BUILD_TYPE = "user"; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Mock private SwitchPreference mPreference; @@ -69,61 +80,76 @@ public class DesktopModePreferenceControllerTest { @Mock private FragmentTransaction mTransaction; + private Resources mResources; private Context mContext; private DesktopModePreferenceController mController; @Before public void setup() { MockitoAnnotations.initMocks(this); - mContext = RuntimeEnvironment.application; + + mContext = spy(ApplicationProvider.getApplicationContext()); doReturn(mTransaction).when(mFragmentManager).beginTransaction(); doReturn(mFragmentManager).when(mActivity).getSupportFragmentManager(); doReturn(mActivity).when(mFragment).getActivity(); + + mResources = spy(mContext.getResources()); + when(mContext.getResources()).thenReturn(mResources); + mController = new DesktopModePreferenceController(mContext, mFragment); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); mController.displayPreference(mScreen); + + // Set desktop mode available + when(mResources.getBoolean(R.bool.config_isDesktopModeSupported)) + .thenReturn(true); + ShadowSystemProperties.override("persist.wm.debug.desktop_mode_enforce_device_restrictions", + "false"); } @Test - public void isAvailable_engBuild_shouldBeTrue() { + public void isAvailable_desktopModeDevOptionNotSupported_returnsFalse() { + mController = spy(mController); + // Dev option is not supported if Desktop mode is not supported + when(mResources.getBoolean(R.bool.config_isDesktopModeSupported)).thenReturn(false); + ShadowSystemProperties.override("persist.wm.debug.desktop_mode_enforce_device_restrictions", + "true"); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_desktopModeDevOptionSupported_returnsTrue() { mController = spy(mController); - doReturn(ENG_BUILD_TYPE).when(mController).getBuildType(); assertThat(mController.isAvailable()).isTrue(); } @Test - public void isAvaiable_userBuild_shouldBeTrue() { - mController = spy(mController); - doReturn(USER_BUILD_TYPE).when(mController).getBuildType(); - - assertThat(mController.isAvailable()).isTrue(); - } - - @Test - public void onPreferenceChange_switchEnabled_shouldEnableDesktopMode() { + public void onPreferenceChange_switchEnabled_putsSettingsOverrideOnAndTriggersRestart() { mController.onPreferenceChange(mPreference, true /* new value */); final int mode = Settings.Global.getInt(mContext.getContentResolver(), - DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, -1 /* default */); + DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, -1 /* default */); assertThat(mode).isEqualTo(SETTING_VALUE_ON); - verify(mTransaction).add(any(RebootConfirmationDialogFragment.class), any()); } @Test - public void onPreferenceChange_switchDisabled_shouldDisableDesktopMode() { + public void onPreferenceChange_switchDisabled_putsSettingsOverrideOffAndTriggersRestart() { mController.onPreferenceChange(mPreference, false /* new value */); - final int mode = Settings.Global.getInt(mContext.getContentResolver(), - DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, -1 /* default */); + int mode = Settings.Global.getInt(mContext.getContentResolver(), + DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, -1 /* default */); assertThat(mode).isEqualTo(SETTING_VALUE_OFF); + verify(mTransaction).add(any(RebootConfirmationDialogFragment.class), any()); } @Test - public void updateState_settingEnabled_preferenceShouldBeChecked() { + public void updateState_overrideOn_checksPreference() { Settings.Global.putInt(mContext.getContentResolver(), - DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, SETTING_VALUE_ON); + DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, SETTING_VALUE_ON); mController.updateState(mPreference); @@ -131,9 +157,9 @@ public class DesktopModePreferenceControllerTest { } @Test - public void updateState_settingDisabled_preferenceShouldNotBeChecked() { + public void updateState_overrideOff_unchecksPreference() { Settings.Global.putInt(mContext.getContentResolver(), - DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, SETTING_VALUE_OFF); + DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, SETTING_VALUE_OFF); mController.updateState(mPreference); @@ -141,12 +167,92 @@ public class DesktopModePreferenceControllerTest { } @Test - public void onDeveloperOptionsSwitchDisabled_shouldDisablePreference() { + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void updateState_overrideUnset_defaultDevOptionStatusOn_checksPreference() { + Settings.Global.putInt(mContext.getContentResolver(), + DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, SETTING_VALUE_UNSET); + + mController.updateState(mPreference); + + verify(mPreference).setChecked(true); + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void updateState_overrideUnset_defaultDevOptionStatusOff_unchecksPreference() { + Settings.Global.putInt(mContext.getContentResolver(), + DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, SETTING_VALUE_UNSET); + + mController.updateState(mPreference); + + verify(mPreference).setChecked(false); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void updateState_noOverride_defaultDevOptionStatusOn_checksPreference() { + // Set no override + Settings.Global.putString(mContext.getContentResolver(), + DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, null); + + mController.updateState(mPreference); + + verify(mPreference).setChecked(true); + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void updateState_noOverride_defaultDevOptionStatusOff_unchecksPreference() { + // Set no override + Settings.Global.putString(mContext.getContentResolver(), + DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, null); + + mController.updateState(mPreference); + + verify(mPreference).setChecked(false); + } + + @Test + public void updateState_noOverride_noNewSettingsOverride() { + // Set no override + Settings.Global.putString(mContext.getContentResolver(), + DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, null); + + mController.updateState(mPreference); + + int mode = Settings.Global.getInt(mContext.getContentResolver(), + DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, -2 /* default */); + assertThat(mode).isEqualTo(-2); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void updateState_overrideUnknown_defaultDevOptionStatusOn_checksPreference() { + Settings.Global.putInt(mContext.getContentResolver(), + DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, -2); + + mController.updateState(mPreference); + + verify(mPreference).setChecked(true); + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void updateState_overrideUnknown_defaultDevOptionStatusOff_unchecksPreference() { + Settings.Global.putInt(mContext.getContentResolver(), + DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, -2); + + mController.updateState(mPreference); + + verify(mPreference).setChecked(false); + } + + @Test + public void onDeveloperOptionsSwitchDisabled_putsSettingsOverrideOff() { mController.onDeveloperOptionsSwitchDisabled(); final int mode = Settings.Global.getInt(mContext.getContentResolver(), - DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, -1 /* default */); - assertThat(mode).isEqualTo(SETTING_VALUE_OFF); - verify(mPreference).setEnabled(false); + DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, -2 /* default */); + assertThat(mode).isEqualTo(DesktopModePreferenceController.SETTING_VALUE_UNSET); } } diff --git a/tests/robotests/src/com/android/settings/development/DesktopModeSecondaryDisplayPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/DesktopModeSecondaryDisplayPreferenceControllerTest.java new file mode 100644 index 00000000000..5931004cb66 --- /dev/null +++ b/tests/robotests/src/com/android/settings/development/DesktopModeSecondaryDisplayPreferenceControllerTest.java @@ -0,0 +1,152 @@ +/* + * 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.development; + +import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS; + +import static com.android.settings.development.DesktopModeSecondaryDisplayPreferenceController.SETTING_VALUE_OFF; +import static com.android.settings.development.DesktopModeSecondaryDisplayPreferenceController.SETTING_VALUE_ON; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.provider.Settings; + +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; +import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreference; + +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; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = { + com.android.settings.testutils.shadow.ShadowFragment.class, +}) +public class DesktopModeSecondaryDisplayPreferenceControllerTest { + + private static final String ENG_BUILD_TYPE = "eng"; + private static final String USER_BUILD_TYPE = "user"; + + @Mock + private SwitchPreference mPreference; + @Mock + private PreferenceScreen mScreen; + @Mock + private DevelopmentSettingsDashboardFragment mFragment; + @Mock + private FragmentActivity mActivity; + @Mock + private FragmentManager mFragmentManager; + @Mock + private FragmentTransaction mTransaction; + + private Context mContext; + private DesktopModeSecondaryDisplayPreferenceController mController; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + doReturn(mTransaction).when(mFragmentManager).beginTransaction(); + doReturn(mFragmentManager).when(mActivity).getSupportFragmentManager(); + doReturn(mActivity).when(mFragment).getActivity(); + mController = new DesktopModeSecondaryDisplayPreferenceController(mContext, mFragment); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); + mController.displayPreference(mScreen); + } + + @Test + public void isAvailable_engBuild_shouldBeTrue() { + mController = spy(mController); + doReturn(ENG_BUILD_TYPE).when(mController).getBuildType(); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void isAvailable_userBuild_shouldBeTrue() { + mController = spy(mController); + doReturn(USER_BUILD_TYPE).when(mController).getBuildType(); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void onPreferenceChange_switchEnabled_enablesDesktopModeOnSecondaryDisplay() { + mController.onPreferenceChange(mPreference, true /* new value */); + + final int mode = Settings.Global.getInt(mContext.getContentResolver(), + DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, -1 /* default */); + assertThat(mode).isEqualTo(SETTING_VALUE_ON); + + verify(mTransaction).add(any(RebootConfirmationDialogFragment.class), any()); + } + + @Test + public void onPreferenceChange_switchDisabled_disablesDesktopModeOnSecondaryDisplay() { + mController.onPreferenceChange(mPreference, false /* new value */); + + final int mode = Settings.Global.getInt(mContext.getContentResolver(), + DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, -1 /* default */); + assertThat(mode).isEqualTo(SETTING_VALUE_OFF); + } + + @Test + public void updateState_settingEnabled_checksPreference() { + Settings.Global.putInt(mContext.getContentResolver(), + DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, SETTING_VALUE_ON); + + mController.updateState(mPreference); + + verify(mPreference).setChecked(true); + } + + @Test + public void updateState_settingDisabled_unchecksPreference() { + Settings.Global.putInt(mContext.getContentResolver(), + DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, SETTING_VALUE_OFF); + + mController.updateState(mPreference); + + verify(mPreference).setChecked(false); + } + + @Test + public void onDeveloperOptionsSwitchDisabled_disablesPreference() { + mController.onDeveloperOptionsSwitchDisabled(); + + final int mode = Settings.Global.getInt(mContext.getContentResolver(), + DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, -1 /* default */); + assertThat(mode).isEqualTo(SETTING_VALUE_OFF); + verify(mPreference).setEnabled(false); + } +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java index cecf8f0f6be..962c40fd81b 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java @@ -77,6 +77,11 @@ public class PowerUsageFeatureProviderImplTest { assertThat(mPowerFeatureProvider.isBatteryTipsEnabled()).isFalse(); } + @Test + public void isRestrictedModeOverwriteEnabled_returnFalse() { + assertThat(mPowerFeatureProvider.isRestrictedModeOverwriteEnabled()).isFalse(); + } + @Test public void isForceExpireAppOptimizationModeEnabled_returnFalse() { assertThat(mPowerFeatureProvider.isForceExpireAppOptimizationModeEnabled()).isFalse(); diff --git a/tests/robotests/src/com/android/settings/notification/modes/TestModeBuilder.java b/tests/robotests/src/com/android/settings/notification/modes/TestModeBuilder.java index 26c7fe170c0..6a1f47409be 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/TestModeBuilder.java +++ b/tests/robotests/src/com/android/settings/notification/modes/TestModeBuilder.java @@ -18,6 +18,7 @@ package com.android.settings.notification.modes; import android.app.AutomaticZenRule; import android.app.NotificationManager; +import android.content.ComponentName; import android.net.Uri; import android.service.notification.Condition; import android.service.notification.ZenDeviceEffects; @@ -149,6 +150,18 @@ class TestModeBuilder { return this; } + TestModeBuilder setConfigurationActivity(ComponentName configActivity) { + mRule.setConfigurationActivity(configActivity); + mConfigZenRule.configurationActivity = configActivity; + return this; + } + + TestModeBuilder setOwner(ComponentName owner) { + mRule.setOwner(owner); + mConfigZenRule.component = owner; + return this; + } + ZenMode build() { return new ZenMode(mId, mRule, mConfigZenRule); } diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java index 31959e5d699..4ba21469fad 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java @@ -19,8 +19,10 @@ package com.android.settings.notification.modes; import static android.app.AutomaticZenRule.TYPE_OTHER; import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR; import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME; +import static android.app.NotificationManager.EXTRA_AUTOMATIC_RULE_ID; import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY; import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; +import static android.service.notification.ConditionProviderService.EXTRA_RULE_ID; import static com.android.settings.notification.modes.ZenModeSetTriggerLinkPreferenceController.AUTOMATIC_TRIGGER_PREF_KEY; @@ -31,10 +33,16 @@ import static org.mockito.Mockito.when; import android.app.AutomaticZenRule; import android.app.Flags; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.pm.ComponentInfo; +import android.content.pm.PackageManager; import android.net.Uri; +import android.os.Bundle; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; +import android.service.notification.ConditionProviderService; import android.service.notification.SystemZenRules; import android.service.notification.ZenModeConfig; @@ -44,6 +52,7 @@ import androidx.test.core.app.ApplicationProvider; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.utils.ZenServiceListing; import com.android.settingslib.PrimarySwitchPreference; import com.android.settingslib.notification.modes.ZenMode; import com.android.settingslib.notification.modes.ZenModesBackend; @@ -71,6 +80,11 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { private PrimarySwitchPreference mPreference; + @Mock + private ZenServiceListing mServiceListing; + @Mock + private PackageManager mPm; + @Mock private PreferenceCategory mPrefCategory; @Mock @@ -84,8 +98,10 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { mContext = ApplicationProvider.getApplicationContext(); mPrefController = new ZenModeSetTriggerLinkPreferenceController(mContext, - "zen_automatic_trigger_category", mFragment, mBackend); + "zen_automatic_trigger_category", mFragment, mBackend, mPm); + mPrefController.setServiceListing(mServiceListing); mPreference = new PrimarySwitchPreference(mContext); + when(mPrefCategory.findPreference(AUTOMATIC_TRIGGER_PREF_KEY)).thenReturn(mPreference); } @@ -93,9 +109,9 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { public void testIsAvailable() { // should not be available for manual DND ZenMode manualMode = ZenMode.manualDndMode(new AutomaticZenRule.Builder("Do Not Disturb", - Uri.parse("manual")) - .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) - .build(), true); + Uri.parse("manual")) + .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) + .build(), true); mPrefController.updateZenMode(mPrefCategory, manualMode); assertThat(mPrefController.isAvailable()).isFalse(); @@ -164,7 +180,7 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { @Test public void testRuleLink_schedule() { ZenModeConfig.ScheduleInfo scheduleInfo = new ZenModeConfig.ScheduleInfo(); - scheduleInfo.days = new int[] { Calendar.MONDAY, Calendar.TUESDAY, Calendar.THURSDAY }; + scheduleInfo.days = new int[]{Calendar.MONDAY, Calendar.TUESDAY, Calendar.THURSDAY}; scheduleInfo.startHour = 1; scheduleInfo.endHour = 15; ZenMode mode = new TestModeBuilder() @@ -237,4 +253,109 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { assertThat(updatedMode.getRule().getOwner()).isEqualTo( ZenModeConfig.getScheduleConditionProvider()); } + + @Test + public void testGetAppRuleIntent_configActivity() throws Exception { + ZenMode mode = new TestModeBuilder() + .setId("id") + .setPackage(mContext.getPackageName()) + .setConfigurationActivity(new ComponentName(mContext.getPackageName(), "test")) + .setType(TYPE_OTHER) + .setTriggerDescription("some rule") + .build(); + + when(mPm.getPackageUid(null, 0)).thenReturn(-1); + when(mPm.getPackageUid(mContext.getPackageName(), 0)).thenReturn(1); + + Intent res = mPrefController.getAppRuleIntent(mode); + assertThat(res).isNotNull(); + assertThat(res.getStringExtra(EXTRA_RULE_ID)).isEqualTo("id"); + assertThat(res.getStringExtra(EXTRA_AUTOMATIC_RULE_ID)).isEqualTo("id"); + assertThat(res.getComponent()).isEqualTo( + new ComponentName(mContext.getPackageName(), "test")); + } + + @Test + public void testGetAppRuleIntent_configActivity_wrongPackage() throws Exception { + ZenMode mode = new TestModeBuilder() + .setPackage(mContext.getPackageName()) + .setConfigurationActivity(new ComponentName("another", "test")) + .setType(TYPE_OTHER) + .build(); + + when(mPm.getPackageUid(null, 0)).thenReturn(-1); + when(mPm.getPackageUid(mContext.getPackageName(), 0)).thenReturn(1); + + Intent res = mPrefController.getAppRuleIntent(mode); + assertThat(res).isNull(); + } + + @Test + public void testGetAppRuleIntent_configActivity_unspecifiedOwner() throws Exception { + ZenMode mode = new TestModeBuilder() + .setId("id") + .setPackage(null) + .setConfigurationActivity(new ComponentName("another", "test")) + .setType(TYPE_OTHER) + .build(); + + when(mPm.getPackageUid(null, 0)).thenReturn(-1); + when(mPm.getPackageUid(mContext.getPackageName(), 0)).thenReturn(1); + + Intent res = mPrefController.getAppRuleIntent(mode); + assertThat(res).isNotNull(); + assertThat(res.getStringExtra(EXTRA_RULE_ID)).isEqualTo("id"); + assertThat(res.getStringExtra(EXTRA_AUTOMATIC_RULE_ID)).isEqualTo("id"); + assertThat(res.getComponent()).isEqualTo(new ComponentName("another", "test")); + } + + @Test + public void testGetAppRuleIntent_cps() throws Exception { + ZenMode mode = new TestModeBuilder() + .setId("id") + .setPackage(mContext.getPackageName()) + .setOwner(new ComponentName(mContext.getPackageName(), "service")) + .build(); + + ComponentInfo ci = new ComponentInfo(); + ci.packageName = mContext.getPackageName(); + ci.metaData = new Bundle(); + ci.metaData.putString(ConditionProviderService.META_DATA_CONFIGURATION_ACTIVITY, + ComponentName.flattenToShortString( + new ComponentName(mContext.getPackageName(), "activity"))); + + when(mServiceListing.findService(new ComponentName(mContext.getPackageName(), "service"))) + .thenReturn(ci); + when(mPm.getPackageUid(null, 0)).thenReturn(-1); + when(mPm.getPackageUid(mContext.getPackageName(), 0)).thenReturn(1); + + Intent res = mPrefController.getAppRuleIntent(mode); + assertThat(res).isNotNull(); + assertThat(res.getStringExtra(EXTRA_RULE_ID)).isEqualTo("id"); + assertThat(res.getStringExtra(EXTRA_AUTOMATIC_RULE_ID)).isEqualTo("id"); + assertThat(res.getComponent()).isEqualTo( + new ComponentName(mContext.getPackageName(), "activity")); + } + + @Test + public void testGetAppRuleIntent_cps_wrongPackage() throws Exception { + ZenMode mode = new TestModeBuilder() + .setPackage("other") + .setOwner(new ComponentName(mContext.getPackageName(), "service")) + .setType(TYPE_OTHER) + .build(); + + ComponentInfo ci = new ComponentInfo(); + ci.packageName = mContext.getPackageName(); + ci.metaData = new Bundle(); + ci.metaData.putString(ConditionProviderService.META_DATA_CONFIGURATION_ACTIVITY, + ComponentName.flattenToShortString( + new ComponentName(mContext.getPackageName(), "activity"))); + + when(mPm.getPackageUid(null, 0)).thenReturn(-1); + when(mPm.getPackageUid(mContext.getPackageName(), 0)).thenReturn(1); + + Intent res = mPrefController.getAppRuleIntent(mode); + assertThat(res).isNull(); + } } diff --git a/tests/spa_unit/src/com/android/settings/spa/network/PrimarySimRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/spa/network/PrimarySimRepositoryTest.kt new file mode 100644 index 00000000000..459afa9c245 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/spa/network/PrimarySimRepositoryTest.kt @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2024 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.spa.network + +import android.content.Context +import android.content.res.Resources +import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settings.R +import com.android.settingslib.spa.widget.preference.ListPreferenceOption +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.stub + +@RunWith(AndroidJUnit4::class) +class PrimarySimRepositoryTest { + @get:Rule + val composeTestRule = createComposeRule() + + private val mockSubscriptionManager = mock { + on { addOnSubscriptionsChangedListener(any(), any()) } doAnswer { + val listener = it.arguments[1] as SubscriptionManager.OnSubscriptionsChangedListener + listener.onSubscriptionsChanged() + } + on { getPhoneNumber(SUB_ID_1) } doReturn NUMBER_1 + on { getPhoneNumber(SUB_ID_2) } doReturn NUMBER_2 + } + + private val context: Context = spy(ApplicationProvider.getApplicationContext()) { + on { getSystemService(SubscriptionManager::class.java) } doReturn mockSubscriptionManager + } + private val spyResources: Resources = spy(context.resources) + + @Test + fun getPrimarySimInfo_oneSim_returnNull() { + val simList = listOf( + SUB_INFO_1, + ) + + val primarySimInfo = PrimarySimRepository(context).getPrimarySimInfo(simList) + + assertThat(primarySimInfo).isNull() + } + + @Test + fun getPrimarySimInfo_verifyCallsList() { + val simList = listOf( + SUB_INFO_1, + SUB_INFO_2 + ) + val expectedList = listOf( + ListPreferenceOption( + id = SUB_INFO_1.subscriptionId, + text = "${SUB_INFO_1.displayName}", + summary = NUMBER_1 + ), + ListPreferenceOption( + id = SUB_INFO_2.subscriptionId, + text = "${SUB_INFO_2.displayName}", + summary = NUMBER_2 + ), + ListPreferenceOption( + id = SubscriptionManager.INVALID_SUBSCRIPTION_ID, + text = context.getString(R.string.sim_calls_ask_first_prefs_title), + ), + ) + + val primarySimInfo = PrimarySimRepository(context).getPrimarySimInfo(simList) + + assertThat(primarySimInfo).isNotNull() + assertThat(primarySimInfo?.callsList).isEqualTo(expectedList) + } + + @Test + fun getPrimarySimInfo_verifySmsList() { + val simList = listOf( + SUB_INFO_1, + SUB_INFO_2 + ) + val expectedList = listOf( + ListPreferenceOption( + id = SUB_INFO_1.subscriptionId, + text = "${SUB_INFO_1.displayName}", + summary = NUMBER_1 + ), + ListPreferenceOption( + id = SUB_INFO_2.subscriptionId, + text = "${SUB_INFO_2.displayName}", + summary = NUMBER_2 + ), + ListPreferenceOption( + id = SubscriptionManager.INVALID_SUBSCRIPTION_ID, + text = context.getString(R.string.sim_calls_ask_first_prefs_title), + ), + ) + + val primarySimInfo = PrimarySimRepository(context).getPrimarySimInfo(simList) + + assertThat(primarySimInfo).isNotNull() + assertThat(primarySimInfo?.smsList).isEqualTo(expectedList) + } + + @Test + fun getPrimarySimInfo_noAskEveryTime_verifySmsList() { + val simList = listOf( + SUB_INFO_1, + SUB_INFO_2 + ) + context.stub { + on { resources } doReturn spyResources + } + spyResources.stub { + on { + getBoolean(com.android.internal.R.bool.config_sms_ask_every_time_support) + } doReturn false + } + val expectedList = listOf( + ListPreferenceOption( + id = SUB_INFO_1.subscriptionId, + text = "${SUB_INFO_1.displayName}", + summary = NUMBER_1 + ), + ListPreferenceOption( + id = SUB_INFO_2.subscriptionId, + text = "${SUB_INFO_2.displayName}", + summary = NUMBER_2 + ), + ) + + val primarySimInfo = PrimarySimRepository(context).getPrimarySimInfo(simList) + + assertThat(primarySimInfo).isNotNull() + assertThat(primarySimInfo?.smsList).isEqualTo(expectedList) + } + + @Test + fun getPrimarySimInfo_verifyDataList() { + val simList = listOf( + SUB_INFO_1, + SUB_INFO_2 + ) + val expectedList = listOf( + ListPreferenceOption( + id = SUB_INFO_1.subscriptionId, + text = "${SUB_INFO_1.displayName}", + summary = NUMBER_1 + ), + ListPreferenceOption( + id = SUB_INFO_2.subscriptionId, + text = "${SUB_INFO_2.displayName}", + summary = NUMBER_2 + ), + ) + + val primarySimInfo = PrimarySimRepository(context).getPrimarySimInfo(simList) + + assertThat(primarySimInfo).isNotNull() + assertThat(primarySimInfo?.dataList).isEqualTo(expectedList) + } + + private companion object { + const val SUB_ID_1 = 1 + const val SUB_ID_2 = 2 + const val DISPLAY_NAME_1 = "Sub 1" + const val DISPLAY_NAME_2 = "Sub 2" + const val NUMBER_1 = "000000001" + const val NUMBER_2 = "000000002" + const val MCC = "310" + + val SUB_INFO_1: SubscriptionInfo = SubscriptionInfo.Builder().apply { + setId(SUB_ID_1) + setDisplayName(DISPLAY_NAME_1) + setMcc(MCC) + }.build() + + val SUB_INFO_2: SubscriptionInfo = SubscriptionInfo.Builder().apply { + setId(SUB_ID_2) + setDisplayName(DISPLAY_NAME_2) + setMcc(MCC) + }.build() + } +} diff --git a/tests/unit/src/com/android/settings/fuelgauge/batteryusage/AppOptModeSharedPreferencesUtilsTest.kt b/tests/unit/src/com/android/settings/fuelgauge/batteryusage/AppOptModeSharedPreferencesUtilsTest.kt index 94f082d4eac..95f69da141d 100644 --- a/tests/unit/src/com/android/settings/fuelgauge/batteryusage/AppOptModeSharedPreferencesUtilsTest.kt +++ b/tests/unit/src/com/android/settings/fuelgauge/batteryusage/AppOptModeSharedPreferencesUtilsTest.kt @@ -83,8 +83,17 @@ class AppOptModeSharedPreferencesUtilsTest { } @Test - fun updateAppOptModeExpirationInternal_withExpirationTime_verifyData() { - insertAppOptModeEventForTest(expirationTime = 1000L) + fun updateAppOptModeExpirationInternal_withoutExpirationTime_verifyEmptyList() { + insertAppOptModeEventForTest(expirationTime = UNLIMITED_EXPIRE_TIME) + + assertThat(AppOptModeSharedPreferencesUtils.getAllEvents(context)).isEmpty() + } + + @Test + fun updateAppOptModeExpirationInternal_setOptimizedModeWithFlagEnabled_verifyData() { + whenever(featureFactory.powerUsageFeatureProvider.isRestrictedModeOverwriteEnabled) + .thenReturn(true) + insertAppOptModeEventForTest(expirationTime = 1000L, mode = MODE_OPTIMIZED) val events = AppOptModeSharedPreferencesUtils.getAllEvents(context) @@ -99,8 +108,46 @@ class AppOptModeSharedPreferencesUtilsTest { } @Test - fun updateAppOptModeExpirationInternal_withoutExpirationTime_verifyEmptyList() { - insertAppOptModeEventForTest(expirationTime = UNLIMITED_EXPIRE_TIME) + fun updateAppOptModeExpirationInternal_setOptimizedModeWithFlagDisabled_verifyData() { + whenever(featureFactory.powerUsageFeatureProvider.isRestrictedModeOverwriteEnabled) + .thenReturn(false) + insertAppOptModeEventForTest(expirationTime = 1000L, mode = MODE_OPTIMIZED) + + val events = AppOptModeSharedPreferencesUtils.getAllEvents(context) + + assertThat(events).hasSize(1) + assertAppOptimizationModeEventInfo( + events[0], + UID, + PACKAGE_NAME, + MODE_OPTIMIZED, + expirationTime = 1000L + ) + } + + @Test + fun updateAppOptModeExpirationInternal_setRestrictedModeWithFlagEnabled_verifyData() { + whenever(featureFactory.powerUsageFeatureProvider.isRestrictedModeOverwriteEnabled) + .thenReturn(true) + insertAppOptModeEventForTest(expirationTime = 1000L, mode = MODE_RESTRICTED) + + val events = AppOptModeSharedPreferencesUtils.getAllEvents(context) + + assertThat(events).hasSize(1) + assertAppOptimizationModeEventInfo( + events[0], + UID, + PACKAGE_NAME, + MODE_RESTRICTED, + expirationTime = 1000L + ) + } + + @Test + fun updateAppOptModeExpirationInternal_setRestrictedModeWithFlagDisabled_verifyEmptyList() { + whenever(featureFactory.powerUsageFeatureProvider.isRestrictedModeOverwriteEnabled) + .thenReturn(false) + insertAppOptModeEventForTest(expirationTime = 1000L, mode = MODE_RESTRICTED) assertThat(AppOptModeSharedPreferencesUtils.getAllEvents(context)).isEmpty() } @@ -237,14 +284,14 @@ class AppOptModeSharedPreferencesUtilsTest { assertThat(currentOptMode).isEqualTo(MODE_RESTRICTED) } - private fun insertAppOptModeEventForTest(expirationTime: Long) { + private fun insertAppOptModeEventForTest(expirationTime: Long, mode: Int = MODE_OPTIMIZED) { whenever(testBatteryOptimizeUtils?.isOptimizeModeMutable).thenReturn(true) - whenever(testBatteryOptimizeUtils?.getAppOptimizationMode(true)).thenReturn(MODE_OPTIMIZED) + whenever(testBatteryOptimizeUtils?.getAppOptimizationMode(true)).thenReturn(mode) AppOptModeSharedPreferencesUtils.updateAppOptModeExpirationInternal( context, mutableListOf(UID), mutableListOf(PACKAGE_NAME), - mutableListOf(MODE_OPTIMIZED), + mutableListOf(mode), longArrayOf(expirationTime), ) { _: Int, _: String -> testBatteryOptimizeUtils