diff --git a/res/values/config.xml b/res/values/config.xml index c7ef595e3ae..fb1859c85b1 100755 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -617,4 +617,7 @@ false + + + false diff --git a/res/values/strings.xml b/res/values/strings.xml index b7007c4b69c..fde4f262b59 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -6646,6 +6646,8 @@ No usage for past 24 hr + + Other users Battery left estimate is based on your device usage diff --git a/res/xml/vpn_settings2.xml b/res/xml/vpn_settings2.xml index 08075a6c8b1..0d374c75518 100644 --- a/res/xml/vpn_settings2.xml +++ b/res/xml/vpn_settings2.xml @@ -16,4 +16,9 @@ + + + diff --git a/src/com/android/settings/display/ScreenResolutionFragment.java b/src/com/android/settings/display/ScreenResolutionFragment.java index bc825149c9c..914d4be568a 100644 --- a/src/com/android/settings/display/ScreenResolutionFragment.java +++ b/src/com/android/settings/display/ScreenResolutionFragment.java @@ -26,6 +26,7 @@ import android.content.res.Resources; import android.graphics.Point; import android.graphics.drawable.Drawable; import android.hardware.display.DisplayManager; +import android.provider.Settings; import android.text.TextUtils; import android.view.Display; @@ -56,6 +57,7 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment { private Resources mResources; private static final int FHD_INDEX = 0; private static final int QHD_INDEX = 1; + private static final String RESOLUTION_METRIC_SETTING_KEY = "user_selected_resolution"; private Display mDefaultDisplay; private String[] mScreenResolutionOptions; private Set mResolutions; @@ -157,6 +159,17 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment { @VisibleForTesting public void setDisplayMode(final int width) { mDisplayObserver.startObserve(); + + /** For store settings globally. */ + /** TODO(b/238061217): Moving to an atom with the same string */ + Settings.System.putString( + getContext().getContentResolver(), + RESOLUTION_METRIC_SETTING_KEY, + getPreferMode(width).getPhysicalWidth() + + "x" + + getPreferMode(width).getPhysicalHeight()); + + /** Apply the resolution change. */ mDefaultDisplay.setUserPreferredDisplayMode(getPreferMode(width)); } @@ -186,7 +199,7 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment { protected boolean setDefaultKey(final String key) { int width = getWidthForResoluitonKey(key); if (width < 0) { - return false; + return false; } setDisplayMode(width); @@ -200,9 +213,8 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment { String selectedKey = selected.getKey(); int selectedWidth = getWidthForResoluitonKey(selectedKey); if (!mDisplayObserver.setPendingResolutionChange(selectedWidth)) { - return; + return; } - super.onRadioButtonClicked(selected); } @@ -318,7 +330,7 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment { } if (!isDensityChanged() || !isResolutionChangeApplied()) { - return; + return; } restoreDensity(); @@ -353,10 +365,10 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment { int currentWidth = getCurrentWidth(); if (selectedWidth == currentWidth) { - return false; + return false; } if (mPreviousWidth.get() != -1 && !isResolutionChangeApplied()) { - return false; + return false; } mPreviousWidth.set(currentWidth); @@ -366,7 +378,7 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment { private boolean isResolutionChangeApplied() { if (mPreviousWidth.get() == getCurrentWidth()) { - return false; + return false; } return true; diff --git a/src/com/android/settings/fuelgauge/BatteryUtils.java b/src/com/android/settings/fuelgauge/BatteryUtils.java index 3f6c1657165..a6c48a48d6b 100644 --- a/src/com/android/settings/fuelgauge/BatteryUtils.java +++ b/src/com/android/settings/fuelgauge/BatteryUtils.java @@ -69,6 +69,8 @@ public class BatteryUtils { public static final int UID_REMOVED_APPS = -4; /** Special UID value for data usage by tethering. */ public static final int UID_TETHERING = -5; + /** Special UID for aggregated other users. */ + public static final long UID_OTHER_USERS = Long.MIN_VALUE; @Retention(RetentionPolicy.SOURCE) @IntDef({StatusType.SCREEN_USAGE, diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java index ee53d7f6b6e..83d7a33287a 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java +++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java @@ -149,6 +149,11 @@ public interface PowerUsageFeatureProvider { */ Map> getBatteryHistory(Context context); + /** + * Returns battery history data since last full charge with corresponding timestamp key. + */ + Map> getBatteryHistorySinceLastFullCharge(Context context); + /** * Returns {@link Uri} to monitor battery history data is update. */ diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java index 8c8fd96965d..12626414e44 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java +++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java @@ -165,6 +165,12 @@ public class PowerUsageFeatureProviderImpl implements PowerUsageFeatureProvider return null; } + @Override + public Map> getBatteryHistorySinceLastFullCharge( + Context context) { + return null; + } + @Override public Uri getBatteryHistoryUri() { return null; diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java index 8b995c859f9..d4d7a00fdc2 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java @@ -113,6 +113,9 @@ public class BatteryDiffEntry { /** Gets the app label name for this entry. */ public String getAppLabel() { + if (isOtherUsers()) { + return mContext.getString(R.string.battery_usage_other_users); + } loadLabelAndIcon(); // Returns default applicationn label if we cannot find it. return mAppLabel == null || mAppLabel.length() == 0 @@ -122,6 +125,9 @@ public class BatteryDiffEntry { /** Gets the app icon {@link Drawable} for this entry. */ public Drawable getAppIcon() { + if (isOtherUsers()) { + return mContext.getDrawable(R.drawable.ic_power_system); + } loadLabelAndIcon(); return mAppIcon != null && mAppIcon.getConstantState() != null ? mAppIcon.getConstantState().newDrawable() @@ -156,6 +162,9 @@ public class BatteryDiffEntry { /** Whether the current BatteryDiffEntry is system component or not. */ public boolean isSystemEntry() { + if (isOtherUsers()) { + return true; + } switch (mBatteryHistEntry.mConsumerType) { case ConvertUtils.CONSUMER_TYPE_USER_BATTERY: case ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY: @@ -175,6 +184,11 @@ public class BatteryDiffEntry { return false; } + private boolean isOtherUsers() { + return mBatteryHistEntry.mConsumerType == ConvertUtils.CONSUMER_TYPE_UID_BATTERY + && mBatteryHistEntry.mUid == BatteryUtils.UID_OTHER_USERS; + } + void loadLabelAndIcon() { if (mIsLoaded) { return; diff --git a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java index 76f9419ac41..168fe0f5383 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java +++ b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java @@ -21,6 +21,7 @@ import android.content.Context; import android.os.BatteryUsageStats; import android.os.LocaleList; import android.os.UserHandle; +import android.os.UserManager; import android.text.format.DateFormat; import android.text.format.DateUtils; import android.util.ArraySet; @@ -28,6 +29,8 @@ import android.util.Log; import androidx.annotation.VisibleForTesting; +import com.android.settings.Utils; +import com.android.settings.fuelgauge.BatteryUtils; import com.android.settings.overlay.FeatureFactory; import java.lang.annotation.Retention; @@ -265,17 +268,55 @@ public final class ConvertUtils { } } insert24HoursData(BatteryChartView.SELECTED_INDEX_ALL, resultMap); + resolveMultiUsersData(context, resultMap); if (purgeLowPercentageAndFakeData) { purgeLowPercentageAndFakeData(context, resultMap); } return resultMap; } + @VisibleForTesting + static void resolveMultiUsersData( + final Context context, + final Map> indexedUsageMap) { + final int currentUserId = context.getUserId(); + final UserHandle userHandle = + Utils.getManagedProfile(context.getSystemService(UserManager.class)); + final int workProfileUserId = + userHandle != null ? userHandle.getIdentifier() : Integer.MIN_VALUE; + // Loops for all BatteryDiffEntry in the different slots. + for (List entryList : indexedUsageMap.values()) { + double consumePowerFromOtherUsers = 0f; + double consumePercentageFromOtherUsers = 0f; + final Iterator iterator = entryList.iterator(); + while (iterator.hasNext()) { + final BatteryDiffEntry entry = iterator.next(); + final BatteryHistEntry batteryHistEntry = entry.mBatteryHistEntry; + if (batteryHistEntry.mConsumerType != CONSUMER_TYPE_UID_BATTERY) { + continue; + } + // Whether the BatteryHistEntry represents the current user data? + if (batteryHistEntry.mUserId == currentUserId + || batteryHistEntry.mUserId == workProfileUserId) { + continue; + } + // Removes and aggregates non-current users data from the list. + iterator.remove(); + consumePowerFromOtherUsers += entry.mConsumePower; + consumePercentageFromOtherUsers += entry.getPercentOfTotal(); + } + if (consumePercentageFromOtherUsers != 0) { + entryList.add(createOtherUsersEntry(context, consumePowerFromOtherUsers, + consumePercentageFromOtherUsers)); + } + } + } + private static void insert24HoursData( final int desiredIndex, final Map> indexedUsageMap) { final Map resultMap = new HashMap<>(); - double totalConsumePower = 0.0; + double totalConsumePower = 0f; // Loops for all BatteryDiffEntry and aggregate them together. for (List entryList : indexedUsageMap.values()) { for (BatteryDiffEntry entry : entryList) { @@ -361,4 +402,22 @@ public final class ConvertUtils { return locales != null && !locales.isEmpty() ? locales.get(0) : Locale.getDefault(); } + + private static BatteryDiffEntry createOtherUsersEntry( + Context context, double consumePower, double consumePercentage) { + final ContentValues values = new ContentValues(); + values.put(BatteryHistEntry.KEY_UID, BatteryUtils.UID_OTHER_USERS); + values.put(BatteryHistEntry.KEY_USER_ID, BatteryUtils.UID_OTHER_USERS); + values.put(BatteryHistEntry.KEY_CONSUMER_TYPE, CONSUMER_TYPE_UID_BATTERY); + // We will show the percentage for the "other users" item only, the aggregated + // running time information is useless for users to identify individual apps. + final BatteryDiffEntry batteryDiffEntry = new BatteryDiffEntry( + context, + /*foregroundUsageTimeInMs=*/ 0, + /*backgroundUsageTimeInMs=*/ 0, + consumePower, + new BatteryHistEntry(values)); + batteryDiffEntry.setTotalConsumePower(100 * consumePower / consumePercentage); + return batteryDiffEntry; + } } diff --git a/src/com/android/settings/overlay/FeatureFactory.java b/src/com/android/settings/overlay/FeatureFactory.java index aff7197e274..c59b3854599 100644 --- a/src/com/android/settings/overlay/FeatureFactory.java +++ b/src/com/android/settings/overlay/FeatureFactory.java @@ -45,6 +45,7 @@ import com.android.settings.security.SecurityFeatureProvider; import com.android.settings.security.SecuritySettingsFeatureProvider; import com.android.settings.slices.SlicesFeatureProvider; import com.android.settings.users.UserFeatureProvider; +import com.android.settings.vpn2.AdvancedVpnFeatureProvider; import com.android.settings.wifi.WifiTrackerLibProvider; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; @@ -177,6 +178,11 @@ public abstract class FeatureFactory { */ public abstract AccessibilityMetricsFeatureProvider getAccessibilityMetricsFeatureProvider(); + /** + * Retrieves implementation for advanced vpn feature. + */ + public abstract AdvancedVpnFeatureProvider getAdvancedVpnFeatureProvider(); + public static final class FactoryNotFoundException extends RuntimeException { public FactoryNotFoundException(Throwable throwable) { super("Unable to create factory. Did you misconfigure Proguard?", throwable); diff --git a/src/com/android/settings/overlay/FeatureFactoryImpl.java b/src/com/android/settings/overlay/FeatureFactoryImpl.java index 89f74de8233..593b8666c02 100644 --- a/src/com/android/settings/overlay/FeatureFactoryImpl.java +++ b/src/com/android/settings/overlay/FeatureFactoryImpl.java @@ -71,6 +71,8 @@ import com.android.settings.slices.SlicesFeatureProvider; import com.android.settings.slices.SlicesFeatureProviderImpl; import com.android.settings.users.UserFeatureProvider; import com.android.settings.users.UserFeatureProviderImpl; +import com.android.settings.vpn2.AdvancedVpnFeatureProvider; +import com.android.settings.vpn2.AdvancedVpnFeatureProviderImpl; import com.android.settings.wifi.WifiTrackerLibProvider; import com.android.settings.wifi.WifiTrackerLibProviderImpl; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; @@ -106,6 +108,7 @@ public class FeatureFactoryImpl extends FeatureFactory { private SecuritySettingsFeatureProvider mSecuritySettingsFeatureProvider; private AccessibilitySearchFeatureProvider mAccessibilitySearchFeatureProvider; private AccessibilityMetricsFeatureProvider mAccessibilityMetricsFeatureProvider; + private AdvancedVpnFeatureProvider mAdvancedVpnFeatureProvider; @Override public SupportFeatureProvider getSupportFeatureProvider(Context context) { @@ -334,4 +337,12 @@ public class FeatureFactoryImpl extends FeatureFactory { } return mAccessibilityMetricsFeatureProvider; } + + @Override + public AdvancedVpnFeatureProvider getAdvancedVpnFeatureProvider() { + if (mAdvancedVpnFeatureProvider == null) { + mAdvancedVpnFeatureProvider = new AdvancedVpnFeatureProviderImpl(); + } + return mAdvancedVpnFeatureProvider; + } } diff --git a/src/com/android/settings/vpn2/AdvancedVpnFeatureProvider.java b/src/com/android/settings/vpn2/AdvancedVpnFeatureProvider.java new file mode 100644 index 00000000000..cb56c351448 --- /dev/null +++ b/src/com/android/settings/vpn2/AdvancedVpnFeatureProvider.java @@ -0,0 +1,50 @@ +/* + * 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.vpn2; + +import android.content.Context; + +/** + * Feature Provider used in vpn usage + */ +public interface AdvancedVpnFeatureProvider { + + /** + * Returns package name of advanced vpn. + */ + String getAdvancedVpnPackageName(); + + /** + * Returns {@code true} advanced vpn is supported. + */ + boolean isAdvancedVpnSupported(Context context); + + /** + * Returns the title of advanced vpn preference group. + */ + String getAdvancedVpnPreferenceGroupTitle(Context context); + + /** + * Returns the title of vpn preference group. + */ + String getVpnPreferenceGroupTitle(Context context); + + /** + * Returns {@code true} advanced vpn is removable. + */ + boolean isAdvancedVpnRemovable(); +} diff --git a/src/com/android/settings/vpn2/AdvancedVpnFeatureProviderImpl.java b/src/com/android/settings/vpn2/AdvancedVpnFeatureProviderImpl.java new file mode 100644 index 00000000000..c5bc69c042d --- /dev/null +++ b/src/com/android/settings/vpn2/AdvancedVpnFeatureProviderImpl.java @@ -0,0 +1,49 @@ +/* + * 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.vpn2; + +import android.content.Context; + +/** + * Feature provider implementation for advanced vpn. + */ +public class AdvancedVpnFeatureProviderImpl implements AdvancedVpnFeatureProvider { + @Override + public String getAdvancedVpnPackageName() { + return null; + } + + @Override + public boolean isAdvancedVpnSupported(Context context) { + return false; + } + + @Override + public String getAdvancedVpnPreferenceGroupTitle(Context context) { + return null; + } + + @Override + public String getVpnPreferenceGroupTitle(Context context) { + return null; + } + + @Override + public boolean isAdvancedVpnRemovable() { + return true; + } +} diff --git a/src/com/android/settings/vpn2/AppManagementFragment.java b/src/com/android/settings/vpn2/AppManagementFragment.java index d4ee5b9c476..d2fa5fccbfc 100644 --- a/src/com/android/settings/vpn2/AppManagementFragment.java +++ b/src/com/android/settings/vpn2/AppManagementFragment.java @@ -48,6 +48,7 @@ import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; import com.android.settings.core.SubSettingLauncher; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; +import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.RestrictedPreference; @@ -71,6 +72,7 @@ public class AppManagementFragment extends SettingsPreferenceFragment private PackageManager mPackageManager; private DevicePolicyManager mDevicePolicyManager; private VpnManager mVpnManager; + private AdvancedVpnFeatureProvider mFeatureProvider; // VPN app info private final int mUserId = UserHandle.myUserId(); @@ -122,6 +124,7 @@ public class AppManagementFragment extends SettingsPreferenceFragment mPackageManager = getContext().getPackageManager(); mDevicePolicyManager = getContext().getSystemService(DevicePolicyManager.class); mVpnManager = getContext().getSystemService(VpnManager.class); + mFeatureProvider = FeatureFactory.getFactory(getContext()).getAdvancedVpnFeatureProvider(); mPreferenceAlwaysOn = (RestrictedSwitchPreference) findPreference(KEY_ALWAYS_ON_VPN); mPreferenceLockdown = (RestrictedSwitchPreference) findPreference(KEY_LOCKDOWN_VPN); @@ -283,7 +286,16 @@ public class AppManagementFragment extends SettingsPreferenceFragment } } - private void updateRestrictedViews() { + @VisibleForTesting + void updateRestrictedViews() { + if (mFeatureProvider.isAdvancedVpnSupported(getContext()) + && !mFeatureProvider.isAdvancedVpnRemovable() + && TextUtils.equals(mPackageName, mFeatureProvider.getAdvancedVpnPackageName())) { + mPreferenceForget.setVisible(false); + } else { + mPreferenceForget.setVisible(true); + } + if (isAdded()) { mPreferenceAlwaysOn.checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_VPN, mUserId); @@ -314,6 +326,14 @@ public class AppManagementFragment extends SettingsPreferenceFragment } } + @VisibleForTesting + void init(String packageName, AdvancedVpnFeatureProvider featureProvider, + RestrictedPreference preference) { + mPackageName = packageName; + mFeatureProvider = featureProvider; + mPreferenceForget = preference; + } + private String getAlwaysOnVpnPackage() { return mVpnManager.getAlwaysOnVpnPackageForUser(mUserId); } diff --git a/src/com/android/settings/vpn2/VpnSettings.java b/src/com/android/settings/vpn2/VpnSettings.java index e89785fe535..43805956b6e 100644 --- a/src/com/android/settings/vpn2/VpnSettings.java +++ b/src/com/android/settings/vpn2/VpnSettings.java @@ -42,6 +42,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.security.Credentials; import android.security.LegacyVpnProfileStore; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; @@ -52,6 +53,7 @@ import android.view.MenuItem; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import androidx.preference.PreferenceGroup; +import androidx.preference.PreferenceScreen; import com.android.internal.annotations.GuardedBy; import com.android.internal.net.LegacyVpnInfo; @@ -59,6 +61,7 @@ import com.android.internal.net.VpnConfig; import com.android.internal.net.VpnProfile; import com.android.settings.R; import com.android.settings.RestrictedSettingsFragment; +import com.android.settings.overlay.FeatureFactory; import com.android.settings.widget.GearPreference; import com.android.settings.widget.GearPreference.OnGearClickListener; import com.android.settingslib.RestrictedLockUtilsInternal; @@ -79,9 +82,12 @@ import java.util.Set; public class VpnSettings extends RestrictedSettingsFragment implements Handler.Callback, Preference.OnPreferenceClickListener { private static final String LOG_TAG = "VpnSettings"; + private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG); private static final int RESCAN_MESSAGE = 0; private static final int RESCAN_INTERVAL_MS = 1000; + private static final String ADVANCED_VPN_GROUP_KEY = "advanced_vpn_group"; + private static final String VPN_GROUP_KEY = "vpn_group"; private static final NetworkRequest VPN_REQUEST = new NetworkRequest.Builder() .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) @@ -102,6 +108,9 @@ public class VpnSettings extends RestrictedSettingsFragment implements private LegacyVpnInfo mConnectedLegacyVpn; private boolean mUnavailable; + private AdvancedVpnFeatureProvider mFeatureProvider; + private PreferenceScreen mPreferenceScreen; + private boolean mIsAdvancedVpnSupported; public VpnSettings() { super(UserManager.DISALLOW_CONFIG_VPN); @@ -119,11 +128,14 @@ public class VpnSettings extends RestrictedSettingsFragment implements mUserManager = (UserManager) getSystemService(Context.USER_SERVICE); mConnectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); mVpnManager = (VpnManager) getSystemService(Context.VPN_MANAGEMENT_SERVICE); + mFeatureProvider = FeatureFactory.getFactory(getContext()).getAdvancedVpnFeatureProvider(); + mIsAdvancedVpnSupported = mFeatureProvider.isAdvancedVpnSupported(getContext()); mUnavailable = isUiRestricted(); setHasOptionsMenu(!mUnavailable); addPreferencesFromResource(R.xml.vpn_settings2); + mPreferenceScreen = getPreferenceScreen(); } @Override @@ -229,7 +241,8 @@ public class VpnSettings extends RestrictedSettingsFragment implements // Run heavy RPCs before switching to UI thread final List vpnProfiles = loadVpnProfiles(); - final List vpnApps = getVpnApps(context, /* includeProfiles */ true); + final List vpnApps = getVpnApps(context, /* includeProfiles */ true, + mFeatureProvider); final Map connectedLegacyVpns = getConnectedLegacyVpns(); final Set connectedAppVpns = getConnectedAppVpns(); @@ -265,7 +278,7 @@ public class VpnSettings extends RestrictedSettingsFragment implements private final VpnSettings mSettings; - public UpdatePreferences(VpnSettings settings) { + UpdatePreferences(VpnSettings settings) { mSettings = settings; } @@ -332,7 +345,14 @@ public class VpnSettings extends RestrictedSettingsFragment implements } // Trim out deleted VPN preferences - mSettings.setShownPreferences(updates); + if (DEBUG) { + Log.d(LOG_TAG, "isAdvancedVpnSupported() : " + mSettings.mIsAdvancedVpnSupported); + } + if (mSettings.mIsAdvancedVpnSupported) { + mSettings.setShownAdvancedPreferences(updates); + } else { + mSettings.setShownPreferences(updates); + } } } @@ -343,12 +363,61 @@ public class VpnSettings extends RestrictedSettingsFragment implements @VisibleForTesting @UiThread public void setShownPreferences(final Collection updates) { + retainAllPreference(updates); + + final PreferenceGroup vpnGroup = getPreferenceScreen(); + updatePreferenceGroup(vpnGroup, updates); + + // Show all new preferences on the screen + for (Preference pref : updates) { + vpnGroup.addPreference(pref); + } + } + + @VisibleForTesting @UiThread + void setShownAdvancedPreferences(final Collection updates) { + retainAllPreference(updates); + + PreferenceGroup advancedVpnGroup = mPreferenceScreen.findPreference(ADVANCED_VPN_GROUP_KEY); + PreferenceGroup vpnGroup = mPreferenceScreen.findPreference(VPN_GROUP_KEY); + advancedVpnGroup.setTitle( + mFeatureProvider.getAdvancedVpnPreferenceGroupTitle(getContext())); + vpnGroup.setTitle(mFeatureProvider.getVpnPreferenceGroupTitle(getContext())); + updatePreferenceGroup(advancedVpnGroup, updates); + updatePreferenceGroup(vpnGroup, updates); + + // Show all new preferences on the screen + for (Preference pref : updates) { + String packageName = ""; + if (pref instanceof LegacyVpnPreference) { + LegacyVpnPreference legacyPref = (LegacyVpnPreference) pref; + packageName = legacyPref.getPackageName(); + } else if (pref instanceof AppPreference) { + AppPreference appPref = (AppPreference) pref; + packageName = appPref.getPackageName(); + } + if (DEBUG) { + Log.d(LOG_TAG, "setShownAdvancedPreferences() package name : " + packageName); + } + if (TextUtils.equals(packageName, mFeatureProvider.getAdvancedVpnPackageName())) { + advancedVpnGroup.addPreference(pref); + } else { + vpnGroup.addPreference(pref); + } + } + + advancedVpnGroup.setVisible(advancedVpnGroup.getPreferenceCount() > 0); + vpnGroup.setVisible(vpnGroup.getPreferenceCount() > 0); + } + + private void retainAllPreference(Collection updates) { mLegacyVpnPreferences.values().retainAll(updates); mAppPreferences.values().retainAll(updates); + } + private void updatePreferenceGroup(PreferenceGroup vpnGroup, Collection updates) { // Change {@param updates} in-place to only contain new preferences that were not already // added to the preference screen. - final PreferenceGroup vpnGroup = getPreferenceScreen(); for (int i = vpnGroup.getPreferenceCount() - 1; i >= 0; i--) { Preference p = vpnGroup.getPreference(i); if (updates.contains(p)) { @@ -357,11 +426,6 @@ public class VpnSettings extends RestrictedSettingsFragment implements vpnGroup.removePreference(p); } } - - // Show any new preferences on the screen - for (Preference pref : updates) { - vpnGroup.addPreference(pref); - } } @Override @@ -508,7 +572,15 @@ public class VpnSettings extends RestrictedSettingsFragment implements return result; } - static List getVpnApps(Context context, boolean includeProfiles) { + static List getVpnApps(Context context, boolean includeProfiles, + AdvancedVpnFeatureProvider featureProvider) { + return getVpnApps(context, includeProfiles, featureProvider, + context.getSystemService(AppOpsManager.class)); + } + + @VisibleForTesting + static List getVpnApps(Context context, boolean includeProfiles, + AdvancedVpnFeatureProvider featureProvider, AppOpsManager aom) { List result = Lists.newArrayList(); final Set profileIds; @@ -521,8 +593,6 @@ public class VpnSettings extends RestrictedSettingsFragment implements profileIds = Collections.singleton(UserHandle.myUserId()); } - // Fetch VPN-enabled apps from AppOps. - AppOpsManager aom = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); List apps = aom.getPackagesForOps(new int[] {OP_ACTIVATE_VPN, OP_ACTIVATE_PLATFORM_VPN}); if (apps != null) { @@ -540,7 +610,7 @@ public class VpnSettings extends RestrictedSettingsFragment implements allowed = true; } } - if (allowed) { + if (allowed || isAdvancedVpn(featureProvider, pkg.getPackageName(), context)) { result.add(new AppVpnInfo(userId, pkg.getPackageName())); } } @@ -550,6 +620,12 @@ public class VpnSettings extends RestrictedSettingsFragment implements return result; } + private static boolean isAdvancedVpn(AdvancedVpnFeatureProvider featureProvider, + String packageName, Context context) { + return featureProvider.isAdvancedVpnSupported(context) + && TextUtils.equals(packageName, featureProvider.getAdvancedVpnPackageName()); + } + private static List loadVpnProfiles() { final ArrayList result = Lists.newArrayList(); @@ -562,4 +638,10 @@ public class VpnSettings extends RestrictedSettingsFragment implements } return result; } + + @VisibleForTesting + void init(PreferenceScreen preferenceScreen, AdvancedVpnFeatureProvider featureProvider) { + mPreferenceScreen = preferenceScreen; + mFeatureProvider = featureProvider; + } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryBackupHelperTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryBackupHelperTest.java index 14bbeea7187..283df7bcc43 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryBackupHelperTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryBackupHelperTest.java @@ -70,6 +70,7 @@ import org.robolectric.annotation.Resetter; import java.util.Arrays; import java.util.List; +import java.util.Set; import java.util.concurrent.TimeUnit; @RunWith(RobolectricTestRunner.class) @@ -343,9 +344,17 @@ public final class BatteryBackupHelperTest { private void verifyBackupData(String expectedResult) throws Exception { final byte[] expectedBytes = expectedResult.getBytes(); + final ArgumentCaptor captor = ArgumentCaptor.forClass(byte[].class); + final Set expectedResultSet = + Set.of(expectedResult.split(BatteryBackupHelper.DELIMITER)); + verify(mBackupDataOutput).writeEntityHeader( BatteryBackupHelper.KEY_OPTIMIZATION_LIST, expectedBytes.length); - verify(mBackupDataOutput).writeEntityData(expectedBytes, expectedBytes.length); + verify(mBackupDataOutput).writeEntityData(captor.capture(), eq(expectedBytes.length)); + final String actualResult = new String(captor.getValue()); + final Set actualResultSet = + Set.of(actualResult.split(BatteryBackupHelper.DELIMITER)); + assertThat(actualResultSet).isEqualTo(expectedResultSet); } private void createTestingData( diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntryTest.java index bb19b5a53dc..683f0fd2df4 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntryTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntryTest.java @@ -138,7 +138,7 @@ public final class BatteryDiffEntryTest { // Generates fake testing data. final ContentValues values = getContentValuesWithType( ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY); - values.put("drainType", + values.put(BatteryHistEntry.KEY_DRAIN_TYPE, Integer.valueOf(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY)); final BatteryHistEntry batteryHistEntry = new BatteryHistEntry(values); @@ -164,7 +164,7 @@ public final class BatteryDiffEntryTest { // Generates fake testing data. final ContentValues values = getContentValuesWithType( ConvertUtils.CONSUMER_TYPE_USER_BATTERY); - values.put("userId", Integer.valueOf(1001)); + values.put(BatteryHistEntry.KEY_USER_ID, Integer.valueOf(1001)); final BatteryHistEntry batteryHistEntry = new BatteryHistEntry(values); final BatteryDiffEntry entry = createBatteryDiffEntry(10, batteryHistEntry); @@ -189,8 +189,8 @@ public final class BatteryDiffEntryTest { final String fakePackageName = "com.fake.google.com"; final ContentValues values = getContentValuesWithType( ConvertUtils.CONSUMER_TYPE_UID_BATTERY); - values.put("uid", /*invalid uid*/ 10001); - values.put("packageName", fakePackageName); + values.put(BatteryHistEntry.KEY_UID, /*invalid uid*/ 10001); + values.put(BatteryHistEntry.KEY_PACKAGE_NAME, fakePackageName); doReturn(mMockAppInfo).when(mMockPackageManager) .getApplicationInfo(fakePackageName, 0); doReturn(expectedAppLabel).when(mMockPackageManager) @@ -233,7 +233,7 @@ public final class BatteryDiffEntryTest { final String expectedAppLabel = "fake app label"; final ContentValues values = getContentValuesWithType( ConvertUtils.CONSUMER_TYPE_UID_BATTERY); - values.put("appLabel", expectedAppLabel); + values.put(BatteryHistEntry.KEY_APP_LABEL, expectedAppLabel); final BatteryHistEntry batteryHistEntry = new BatteryHistEntry(values); final BatteryDiffEntry entry = createBatteryDiffEntry(10, batteryHistEntry); @@ -391,8 +391,8 @@ public final class BatteryDiffEntryTest { final String fakePackageName = "com.fake.google.com"; final ContentValues values = getContentValuesWithType( ConvertUtils.CONSUMER_TYPE_UID_BATTERY); - values.put("uid", /*invalid uid*/ 10001); - values.put("packageName", fakePackageName); + values.put(BatteryHistEntry.KEY_UID, /*invalid uid*/ 10001); + values.put(BatteryHistEntry.KEY_PACKAGE_NAME, fakePackageName); final BatteryDiffEntry entry = createBatteryDiffEntry(10, new BatteryHistEntry(values)); @@ -424,7 +424,7 @@ public final class BatteryDiffEntryTest { final String expectedPackageName = "com.fake.google.com"; final ContentValues values = getContentValuesWithType( ConvertUtils.CONSUMER_TYPE_UID_BATTERY); - values.put("packageName", expectedPackageName); + values.put(BatteryHistEntry.KEY_PACKAGE_NAME, expectedPackageName); final BatteryDiffEntry entry = createBatteryDiffEntry(10, new BatteryHistEntry(values)); @@ -437,7 +437,7 @@ public final class BatteryDiffEntryTest { final ContentValues values = getContentValuesWithType( ConvertUtils.CONSUMER_TYPE_UID_BATTERY); values.put( - "packageName", + BatteryHistEntry.KEY_PACKAGE_NAME, expectedPackageName + ":privileged_process0"); final BatteryDiffEntry entry = createBatteryDiffEntry(10, new BatteryHistEntry(values)); @@ -445,11 +445,24 @@ public final class BatteryDiffEntryTest { assertThat(entry.getPackageName()).isEqualTo(expectedPackageName); } + @Test + public void getAppLabel_withOtherUsersUid_returnExpectedLabel() { + final ContentValues values = getContentValuesWithType( + ConvertUtils.CONSUMER_TYPE_UID_BATTERY); + values.put(BatteryHistEntry.KEY_UID, BatteryUtils.UID_OTHER_USERS); + + final BatteryDiffEntry batteryDiffEntry = createBatteryDiffEntry( + /*consumePower=*/ 0, new BatteryHistEntry(values)); + + assertThat(batteryDiffEntry.getAppLabel()) + .isEqualTo(mContext.getString(R.string.battery_usage_other_users)); + } + private BatteryDiffEntry createBatteryDiffEntry( int consumerType, long uid, boolean isHidden) { final ContentValues values = getContentValuesWithType(consumerType); - values.put("isHidden", isHidden); - values.put("uid", uid); + values.put(BatteryHistEntry.KEY_IS_HIDDEN, isHidden); + values.put(BatteryHistEntry.KEY_UID, uid); return new BatteryDiffEntry( mContext, /*foregroundUsageTimeInMs=*/ 0, @@ -472,15 +485,15 @@ public final class BatteryDiffEntryTest { private static ContentValues getContentValuesWithType(int consumerType) { final ContentValues values = new ContentValues(); - values.put("consumerType", Integer.valueOf(consumerType)); + values.put(BatteryHistEntry.KEY_CONSUMER_TYPE, Integer.valueOf(consumerType)); return values; } private BatteryDiffEntry createBatteryDiffEntry(Drawable drawable) throws Exception { final ContentValues values = getContentValuesWithType( ConvertUtils.CONSUMER_TYPE_UID_BATTERY); - values.put("uid", 1001); - values.put("packageName", "com.a.b.c"); + values.put(BatteryHistEntry.KEY_UID, 1001); + values.put(BatteryHistEntry.KEY_PACKAGE_NAME, "com.a.b.c"); final BatteryHistEntry batteryHistEntry = new BatteryHistEntry(values); doReturn(drawable).when(mMockPackageManager).getDefaultActivityIcon(); doReturn(null).when(mMockPackageManager).getApplicationInfo("com.a.b.c", 0); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java index 0b8a98a742d..c1f981539c1 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java @@ -27,6 +27,7 @@ import android.os.BatteryUsageStats; import android.os.LocaleList; import android.os.UserHandle; +import com.android.settings.fuelgauge.BatteryUtils; import com.android.settings.fuelgauge.PowerUsageFeatureProvider; import com.android.settings.testutils.FakeFeatureFactory; @@ -39,6 +40,7 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import java.util.Arrays; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -371,6 +373,71 @@ public final class ConvertUtilsTest { assertThat(ConvertUtils.getLocale(mContext)).isEqualTo(Locale.getDefault()); } + @Test + public void resolveMultiUsersData_replaceOtherUsersItemWithExpectedEntry() { + final int currentUserId = mContext.getUserId(); + final Map> entryMap = new HashMap<>(); + // Without other users time slot. + entryMap.put(0, Arrays.asList( + createBatteryDiffEntry( + currentUserId, + ConvertUtils.CONSUMER_TYPE_UID_BATTERY, + /*consumePercentage=*/ 50))); + // With other users time slot. + final List withOtherUsersList = new ArrayList<>(); + entryMap.put(1, withOtherUsersList); + withOtherUsersList.add( + createBatteryDiffEntry( + currentUserId + 1, + ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY, + /*consumePercentage=*/ 20)); + withOtherUsersList.add( + createBatteryDiffEntry( + currentUserId + 2, + ConvertUtils.CONSUMER_TYPE_UID_BATTERY, + /*consumePercentage=*/ 30)); + withOtherUsersList.add( + createBatteryDiffEntry( + currentUserId + 3, + ConvertUtils.CONSUMER_TYPE_UID_BATTERY, + /*consumePercentage=*/ 40)); + + ConvertUtils.resolveMultiUsersData(mContext, entryMap); + + assertThat(entryMap.get(0).get(0).getPercentOfTotal()).isEqualTo(50); + // Asserts with other users items. + final List entryList = entryMap.get(1); + assertThat(entryList).hasSize(2); + assertBatteryDiffEntry( + entryList.get(0), + currentUserId + 1, + /*uid=*/ 0, + ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY, + /*consumePercentage=*/ 20); + assertBatteryDiffEntry( + entryList.get(1), + BatteryUtils.UID_OTHER_USERS, + BatteryUtils.UID_OTHER_USERS, + ConvertUtils.CONSUMER_TYPE_UID_BATTERY, + /*consumePercentage=*/ 70); + } + + private BatteryDiffEntry createBatteryDiffEntry( + long userId, int counsumerType, double consumePercentage) { + final ContentValues values = new ContentValues(); + values.put(BatteryHistEntry.KEY_USER_ID, userId); + values.put(BatteryHistEntry.KEY_CONSUMER_TYPE, counsumerType); + final BatteryDiffEntry batteryDiffEntry = + new BatteryDiffEntry( + mContext, + /*foregroundUsageTimeInMs=*/ 0, + /*backgroundUsageTimeInMs=*/ 0, + /*consumePower=*/ consumePercentage, + new BatteryHistEntry(values)); + batteryDiffEntry.setTotalConsumePower(100f); + return batteryDiffEntry; + } + private static BatteryHistEntry createBatteryHistEntry( String packageName, String appLabel, double consumePower, long uid, long foregroundUsageTimeInMs, long backgroundUsageTimeInMs) { @@ -389,6 +456,15 @@ public final class ConvertUtilsTest { return new BatteryHistEntry(values); } + private static void assertBatteryDiffEntry( + BatteryDiffEntry entry, long userId, long uid, int counsumerType, + double consumePercentage) { + assertThat(entry.mBatteryHistEntry.mUid).isEqualTo(uid); + assertThat(entry.mBatteryHistEntry.mUserId).isEqualTo(userId); + assertThat(entry.mBatteryHistEntry.mConsumerType).isEqualTo(counsumerType); + assertThat(entry.getPercentOfTotal()).isEqualTo(consumePercentage); + } + private static void assertBatteryDiffEntry( BatteryDiffEntry entry, int percentOfTotal, long foregroundUsageTimeInMs, long backgroundUsageTimeInMs) { diff --git a/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java b/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java index 518aee9d23a..2f248320ae5 100644 --- a/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java +++ b/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java @@ -47,6 +47,7 @@ import com.android.settings.security.SecurityFeatureProvider; import com.android.settings.security.SecuritySettingsFeatureProvider; import com.android.settings.slices.SlicesFeatureProvider; import com.android.settings.users.UserFeatureProvider; +import com.android.settings.vpn2.AdvancedVpnFeatureProvider; import com.android.settings.wifi.WifiTrackerLibProvider; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; @@ -87,6 +88,7 @@ public class FakeFeatureFactory extends FeatureFactory { public SecuritySettingsFeatureProvider securitySettingsFeatureProvider; public AccessibilitySearchFeatureProvider mAccessibilitySearchFeatureProvider; public AccessibilityMetricsFeatureProvider mAccessibilityMetricsFeatureProvider; + public AdvancedVpnFeatureProvider mAdvancedVpnFeatureProvider; /** * Call this in {@code @Before} method of the test class to use fake factory. @@ -136,6 +138,7 @@ public class FakeFeatureFactory extends FeatureFactory { securitySettingsFeatureProvider = mock(SecuritySettingsFeatureProvider.class); mAccessibilitySearchFeatureProvider = mock(AccessibilitySearchFeatureProvider.class); mAccessibilityMetricsFeatureProvider = mock(AccessibilityMetricsFeatureProvider.class); + mAdvancedVpnFeatureProvider = mock(AdvancedVpnFeatureProvider.class); } @Override @@ -272,4 +275,9 @@ public class FakeFeatureFactory extends FeatureFactory { public AccessibilityMetricsFeatureProvider getAccessibilityMetricsFeatureProvider() { return mAccessibilityMetricsFeatureProvider; } + + @Override + public AdvancedVpnFeatureProvider getAdvancedVpnFeatureProvider() { + return mAdvancedVpnFeatureProvider; + } } diff --git a/tests/unit/src/com/android/settings/display/ScreenResolutionFragmentTest.java b/tests/unit/src/com/android/settings/display/ScreenResolutionFragmentTest.java index b7d37df0d19..4e674d271cb 100644 --- a/tests/unit/src/com/android/settings/display/ScreenResolutionFragmentTest.java +++ b/tests/unit/src/com/android/settings/display/ScreenResolutionFragmentTest.java @@ -27,15 +27,12 @@ import android.view.Display; import androidx.test.annotation.UiThreadTest; import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.settingslib.widget.SelectorWithWidgetPreference; import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; -@RunWith(AndroidJUnit4.class) public class ScreenResolutionFragmentTest { private Context mContext; @@ -56,6 +53,7 @@ public class ScreenResolutionFragmentTest { public void getDefaultKey_FHD() { Display.Mode mode = new Display.Mode(0, FHD_WIDTH, 0, 0); doReturn(mode).when(mFragment).getDisplayMode(); + doReturn(mContext).when(mFragment).getContext(); mFragment.onAttach(mContext); assertThat(mFragment.getDefaultKey()).isEqualTo(mFragment.getKeyForResolution(FHD_WIDTH)); @@ -66,6 +64,7 @@ public class ScreenResolutionFragmentTest { public void getDefaultKey_QHD() { Display.Mode mode = new Display.Mode(0, QHD_WIDTH, 0, 0); doReturn(mode).when(mFragment).getDisplayMode(); + doReturn(mContext).when(mFragment).getContext(); mFragment.onAttach(mContext); assertThat(mFragment.getDefaultKey()).isEqualTo(mFragment.getKeyForResolution(QHD_WIDTH)); @@ -74,6 +73,7 @@ public class ScreenResolutionFragmentTest { @Test @UiThreadTest public void setDefaultKey_FHD() { + doReturn(mContext).when(mFragment).getContext(); mFragment.onAttach(mContext); mFragment.setDefaultKey(mFragment.getKeyForResolution(FHD_WIDTH)); @@ -84,6 +84,7 @@ public class ScreenResolutionFragmentTest { @Test @UiThreadTest public void setDefaultKey_QHD() { + doReturn(mContext).when(mFragment).getContext(); mFragment.onAttach(mContext); mFragment.setDefaultKey(mFragment.getKeyForResolution(QHD_WIDTH)); @@ -94,6 +95,7 @@ public class ScreenResolutionFragmentTest { @Test @UiThreadTest public void bindPreferenceExtra_setSummary() { + doReturn(mContext).when(mFragment).getContext(); mFragment.onAttach(mContext); SelectorWithWidgetPreference preference = new SelectorWithWidgetPreference(mContext); ScreenResolutionFragment.ScreenResolutionCandidateInfo candidates = diff --git a/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java b/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java index 717de791c55..f565075736d 100644 --- a/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java +++ b/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java @@ -45,6 +45,7 @@ import com.android.settings.security.SecurityFeatureProvider; import com.android.settings.security.SecuritySettingsFeatureProvider; import com.android.settings.slices.SlicesFeatureProvider; import com.android.settings.users.UserFeatureProvider; +import com.android.settings.vpn2.AdvancedVpnFeatureProvider; import com.android.settings.wifi.WifiTrackerLibProvider; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; @@ -82,6 +83,7 @@ public class FakeFeatureFactory extends FeatureFactory { public SecuritySettingsFeatureProvider securitySettingsFeatureProvider; public AccessibilitySearchFeatureProvider mAccessibilitySearchFeatureProvider; public AccessibilityMetricsFeatureProvider mAccessibilityMetricsFeatureProvider; + public AdvancedVpnFeatureProvider mAdvancedVpnFeatureProvider; /** * Call this in {@code @Before} method of the test class to use fake factory. @@ -122,6 +124,7 @@ public class FakeFeatureFactory extends FeatureFactory { securitySettingsFeatureProvider = mock(SecuritySettingsFeatureProvider.class); mAccessibilitySearchFeatureProvider = mock(AccessibilitySearchFeatureProvider.class); mAccessibilityMetricsFeatureProvider = mock(AccessibilityMetricsFeatureProvider.class); + mAdvancedVpnFeatureProvider = mock(AdvancedVpnFeatureProvider.class); } @Override @@ -258,4 +261,9 @@ public class FakeFeatureFactory extends FeatureFactory { public AccessibilityMetricsFeatureProvider getAccessibilityMetricsFeatureProvider() { return mAccessibilityMetricsFeatureProvider; } + + @Override + public AdvancedVpnFeatureProvider getAdvancedVpnFeatureProvider() { + return mAdvancedVpnFeatureProvider; + } } diff --git a/tests/unit/src/com/android/settings/vpn2/AppManagementFragmentTest.java b/tests/unit/src/com/android/settings/vpn2/AppManagementFragmentTest.java new file mode 100644 index 00000000000..80bb39372c0 --- /dev/null +++ b/tests/unit/src/com/android/settings/vpn2/AppManagementFragmentTest.java @@ -0,0 +1,102 @@ +/* + * 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.vpn2; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.Looper; + +import androidx.test.annotation.UiThreadTest; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settingslib.RestrictedPreference; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@RunWith(AndroidJUnit4.class) +public class AppManagementFragmentTest { + private static final String FAKE_PACKAGE_NAME = "com.fake.package.name"; + private static final String ADVANCED_VPN_GROUP_PACKAGE_NAME = "com.advanced.package.name"; + + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + + private AppManagementFragment mFragment; + private Context mContext; + private FakeFeatureFactory mFakeFeatureFactory; + private RestrictedPreference mPreferenceForget; + + @Before + @UiThreadTest + public void setUp() { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + + mFragment = spy(new AppManagementFragment()); + mContext = spy(ApplicationProvider.getApplicationContext()); + mPreferenceForget = new RestrictedPreference(mContext); + + mFakeFeatureFactory = FakeFeatureFactory.setupForTest(); + mFragment.init(ADVANCED_VPN_GROUP_PACKAGE_NAME, + mFakeFeatureFactory.getAdvancedVpnFeatureProvider(), mPreferenceForget); + when(mFakeFeatureFactory.mAdvancedVpnFeatureProvider.getAdvancedVpnPackageName()) + .thenReturn(ADVANCED_VPN_GROUP_PACKAGE_NAME); + when(mFakeFeatureFactory.mAdvancedVpnFeatureProvider.isAdvancedVpnSupported(any())) + .thenReturn(true); + } + + @Test + public void updateRestrictedViews_isAdvancedVpn_hidesForgetPreference() { + when(mFakeFeatureFactory.mAdvancedVpnFeatureProvider.isAdvancedVpnRemovable()) + .thenReturn(false); + mFragment.updateRestrictedViews(); + assertThat(mPreferenceForget.isVisible()).isFalse(); + } + + @Test + public void updateRestrictedViews_isNotAdvancedVpn_showsForgetPreference() { + when(mFakeFeatureFactory.mAdvancedVpnFeatureProvider.isAdvancedVpnRemovable()) + .thenReturn(false); + mFragment.init(FAKE_PACKAGE_NAME, + mFakeFeatureFactory.getAdvancedVpnFeatureProvider(), mPreferenceForget); + mFragment.updateRestrictedViews(); + assertThat(mPreferenceForget.isVisible()).isTrue(); + } + + @Test + public void updateRestrictedViews_isAdvancedVpnRemovable_showsForgetPreference() { + when(mFakeFeatureFactory.mAdvancedVpnFeatureProvider.isAdvancedVpnRemovable()) + .thenReturn(true); + mFragment.init(FAKE_PACKAGE_NAME, + mFakeFeatureFactory.getAdvancedVpnFeatureProvider(), mPreferenceForget); + mFragment.updateRestrictedViews(); + assertThat(mPreferenceForget.isVisible()).isTrue(); + } +} diff --git a/tests/unit/src/com/android/settings/vpn2/VpnSettingsTest.java b/tests/unit/src/com/android/settings/vpn2/VpnSettingsTest.java new file mode 100644 index 00000000000..86bd1e741a2 --- /dev/null +++ b/tests/unit/src/com/android/settings/vpn2/VpnSettingsTest.java @@ -0,0 +1,185 @@ +/* + * 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.vpn2; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.app.AppOpsManager; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Looper; +import android.os.UserHandle; +import android.util.ArraySet; + +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceGroup; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; +import androidx.test.annotation.UiThreadTest; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.settings.testutils.FakeFeatureFactory; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +@RunWith(AndroidJUnit4.class) +public class VpnSettingsTest { + private static final String ADVANCED_VPN_GROUP_KEY = "advanced_vpn_group"; + private static final String VPN_GROUP_KEY = "vpn_group"; + private static final String ADVANCED_VPN_GROUP_TITLE = "advanced_vpn_group_title"; + private static final String VPN_GROUP_TITLE = "vpn_group_title"; + private static final String FAKE_PACKAGE_NAME = "com.fake.package.name"; + private static final String ADVANCED_VPN_GROUP_PACKAGE_NAME = "com.advanced.package.name"; + private static final int USER_ID_1 = UserHandle.USER_NULL; + + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + + @Mock + private AppOpsManager mAppOpsManager; + + private VpnSettings mVpnSettings; + private Context mContext; + private PreferenceManager mPreferenceManager; + private PreferenceScreen mPreferenceScreen; + private PreferenceGroup mAdvancedVpnGroup; + private PreferenceGroup mVpnGroup; + private FakeFeatureFactory mFakeFeatureFactory; + + @Before + @UiThreadTest + public void setUp() throws PackageManager.NameNotFoundException { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + + mVpnSettings = spy(new VpnSettings()); + mContext = spy(ApplicationProvider.getApplicationContext()); + mAdvancedVpnGroup = spy(new PreferenceCategory(mContext)); + mVpnGroup = spy(new PreferenceCategory(mContext)); + mAdvancedVpnGroup.setKey(ADVANCED_VPN_GROUP_KEY); + mVpnGroup.setKey(VPN_GROUP_KEY); + mPreferenceManager = new PreferenceManager(mContext); + mPreferenceScreen = mPreferenceManager.createPreferenceScreen(mContext); + mPreferenceScreen.addPreference(mAdvancedVpnGroup); + mPreferenceScreen.addPreference(mVpnGroup); + mFakeFeatureFactory = FakeFeatureFactory.setupForTest(); + mVpnSettings.init(mPreferenceScreen, mFakeFeatureFactory.getAdvancedVpnFeatureProvider()); + + when(mVpnSettings.getContext()).thenReturn(mContext); + when(mFakeFeatureFactory.mAdvancedVpnFeatureProvider + .getAdvancedVpnPreferenceGroupTitle(mContext)).thenReturn(ADVANCED_VPN_GROUP_TITLE); + when(mFakeFeatureFactory.mAdvancedVpnFeatureProvider.getVpnPreferenceGroupTitle(mContext)) + .thenReturn(VPN_GROUP_TITLE); + when(mFakeFeatureFactory.mAdvancedVpnFeatureProvider.getAdvancedVpnPackageName()) + .thenReturn(ADVANCED_VPN_GROUP_PACKAGE_NAME); + when(mFakeFeatureFactory.mAdvancedVpnFeatureProvider.isAdvancedVpnSupported(any())) + .thenReturn(true); + doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt()); + doReturn(mContext).when(mContext).createPackageContextAsUser(any(), anyInt(), any()); + doReturn(mPreferenceManager).when(mVpnGroup).getPreferenceManager(); + doReturn(mPreferenceManager).when(mAdvancedVpnGroup).getPreferenceManager(); + } + + @Test + public void setShownAdvancedPreferences_hasGeneralVpn_returnsVpnCountAs1() { + Set updates = new ArraySet<>(); + AppPreference pref = + spy(new AppPreference(mContext, USER_ID_1, FAKE_PACKAGE_NAME)); + updates.add(pref); + + mVpnSettings.setShownAdvancedPreferences(updates); + + assertThat(mVpnGroup.getPreferenceCount()).isEqualTo(1); + assertThat(mVpnGroup.isVisible()).isTrue(); + assertThat(mAdvancedVpnGroup.isVisible()).isFalse(); + } + + @Test + public void setShownAdvancedPreferences_hasAdvancedVpn_returnsAdvancedVpnCountAs1() { + Set updates = new ArraySet<>(); + AppPreference pref = + spy(new AppPreference(mContext, USER_ID_1, ADVANCED_VPN_GROUP_PACKAGE_NAME)); + updates.add(pref); + + mVpnSettings.setShownAdvancedPreferences(updates); + + assertThat(mAdvancedVpnGroup.getPreferenceCount()).isEqualTo(1); + assertThat(mAdvancedVpnGroup.isVisible()).isTrue(); + assertThat(mVpnGroup.isVisible()).isFalse(); + } + + @Test + public void setShownAdvancedPreferences_noVpn_returnsEmpty() { + Set updates = new ArraySet<>(); + + mVpnSettings.setShownAdvancedPreferences(updates); + + assertThat(mAdvancedVpnGroup.getPreferenceCount()).isEqualTo(0); + assertThat(mVpnGroup.getPreferenceCount()).isEqualTo(0); + assertThat(mAdvancedVpnGroup.isVisible()).isFalse(); + assertThat(mVpnGroup.isVisible()).isFalse(); + } + + @Test + public void getVpnApps_isAdvancedVpn_returnsOne() { + int uid = 1111; + List opEntries = new ArrayList<>(); + List apps = new ArrayList<>(); + AppOpsManager.PackageOps packageOps = + new AppOpsManager.PackageOps(ADVANCED_VPN_GROUP_PACKAGE_NAME, uid, opEntries); + apps.add(packageOps); + when(mAppOpsManager.getPackagesForOps((int[]) any())).thenReturn(apps); + + assertThat(VpnSettings.getVpnApps(mContext, /* includeProfiles= */ false, + mFakeFeatureFactory.getAdvancedVpnFeatureProvider(), + mAppOpsManager).size()).isEqualTo(1); + } + + @Test + public void getVpnApps_isNotAdvancedVpn_returnsEmpty() { + int uid = 1111; + List opEntries = new ArrayList<>(); + List apps = new ArrayList<>(); + AppOpsManager.PackageOps packageOps = + new AppOpsManager.PackageOps(FAKE_PACKAGE_NAME, uid, opEntries); + apps.add(packageOps); + when(mAppOpsManager.getPackagesForOps((int[]) any())).thenReturn(apps); + + assertThat(VpnSettings.getVpnApps(mContext, /* includeProfiles= */ false, + mFakeFeatureFactory.getAdvancedVpnFeatureProvider(), + mAppOpsManager)).isEmpty(); + } +}