diff --git a/res/xml/unrestricted_data_access_settings.xml b/res/xml/unrestricted_data_access_settings.xml index da8ff0cd5d1..b1abb8e212c 100644 --- a/res/xml/unrestricted_data_access_settings.xml +++ b/res/xml/unrestricted_data_access_settings.xml @@ -17,4 +17,8 @@ + xmlns:settings="http://schemas.android.com/apk/res-auto" + android:key="unrestricted_data" + android:title="@string/unrestricted_data_saver" + settings:controller="com.android.settings.datausage.UnrestrictedDataAccessPreferenceController"> + diff --git a/src/com/android/settings/datausage/UnrestrictedDataAccess.java b/src/com/android/settings/datausage/UnrestrictedDataAccess.java index 7aa716c491b..17fb67e5eff 100644 --- a/src/com/android/settings/datausage/UnrestrictedDataAccess.java +++ b/src/com/android/settings/datausage/UnrestrictedDataAccess.java @@ -14,64 +14,35 @@ package com.android.settings.datausage; -import static com.android.settingslib.RestrictedLockUtils.checkIfMeteredDataRestricted; - -import android.app.Application; import android.content.Context; import android.os.Bundle; -import android.os.UserHandle; -import androidx.preference.Preference; -import androidx.preference.PreferenceViewHolder; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; -import com.android.settings.SettingsPreferenceFragment; -import com.android.settings.applications.AppStateBaseBridge; -import com.android.settings.applications.appinfo.AppInfoDashboardFragment; -import com.android.settings.datausage.AppStateDataUsageBridge.DataUsageState; -import com.android.settings.overlay.FeatureFactory; -import com.android.settings.widget.AppSwitchPreference; -import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; -import com.android.settingslib.RestrictedPreferenceHelper; +import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.applications.ApplicationsState; -import com.android.settingslib.applications.ApplicationsState.AppEntry; import com.android.settingslib.applications.ApplicationsState.AppFilter; -import java.util.ArrayList; +public class UnrestrictedDataAccess extends DashboardFragment { -public class UnrestrictedDataAccess extends SettingsPreferenceFragment - implements ApplicationsState.Callbacks, AppStateBaseBridge.Callback, - Preference.OnPreferenceChangeListener { + private static final String TAG = "UnrestrictedDataAccess"; private static final int MENU_SHOW_SYSTEM = Menu.FIRST + 42; private static final String EXTRA_SHOW_SYSTEM = "show_system"; - private ApplicationsState mApplicationsState; - private AppStateDataUsageBridge mDataUsageBridge; - private ApplicationsState.Session mSession; - private DataSaverBackend mDataSaverBackend; private boolean mShowSystem; - private boolean mExtraLoaded; private AppFilter mFilter; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); - setAnimationAllowed(true); - mApplicationsState = ApplicationsState.getInstance( - (Application) getContext().getApplicationContext()); - mDataSaverBackend = new DataSaverBackend(getContext()); - mDataUsageBridge = new AppStateDataUsageBridge(mApplicationsState, this, mDataSaverBackend); - mSession = mApplicationsState.newSession(this, getLifecycle()); mShowSystem = icicle != null && icicle.getBoolean(EXTRA_SHOW_SYSTEM); - mFilter = mShowSystem ? ApplicationsState.FILTER_ALL_ENABLED - : ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER; - setHasOptionsMenu(true); + + use(UnrestrictedDataAccessPreferenceController.class).setParentFragment(this); } @Override @@ -89,9 +60,10 @@ public class UnrestrictedDataAccess extends SettingsPreferenceFragment item.setTitle(mShowSystem ? R.string.menu_hide_system : R.string.menu_show_system); mFilter = mShowSystem ? ApplicationsState.FILTER_ALL_ENABLED : ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER; - if (mExtraLoaded) { - rebuild(); - } + + use(UnrestrictedDataAccessPreferenceController.class).setFilter(mFilter); + use(UnrestrictedDataAccessPreferenceController.class).rebuild(); + break; } return super.onOptionsItemSelected(item); @@ -106,31 +78,15 @@ public class UnrestrictedDataAccess extends SettingsPreferenceFragment @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - setLoading(true, false); } @Override - public void onResume() { - super.onResume(); - mDataUsageBridge.resume(); - } - - @Override - public void onPause() { - super.onPause(); - mDataUsageBridge.pause(); - } - - @Override - public void onDestroy() { - super.onDestroy(); - mDataUsageBridge.release(); - } - - @Override - public void onExtraInfoUpdated() { - mExtraLoaded = true; - rebuild(); + public void onAttach(Context context) { + super.onAttach(context); + mFilter = mShowSystem ? ApplicationsState.FILTER_ALL_ENABLED + : ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER; + use(UnrestrictedDataAccessPreferenceController.class).setSession(getLifecycle()); + use(UnrestrictedDataAccessPreferenceController.class).setFilter(mFilter); } @Override @@ -138,74 +94,9 @@ public class UnrestrictedDataAccess extends SettingsPreferenceFragment return R.string.help_url_unrestricted_data_access; } - private void rebuild() { - ArrayList apps = mSession.rebuild(mFilter, ApplicationsState.ALPHA_COMPARATOR); - if (apps != null) { - onRebuildComplete(apps); - } - } - @Override - public void onRunningStateChanged(boolean running) { - - } - - @Override - public void onPackageListChanged() { - - } - - @Override - public void onRebuildComplete(ArrayList apps) { - if (getContext() == null) return; - cacheRemoveAllPrefs(getPreferenceScreen()); - final int N = apps.size(); - for (int i = 0; i < N; i++) { - AppEntry entry = apps.get(i); - if (!shouldAddPreference(entry)) { - continue; - } - String key = entry.info.packageName + "|" + entry.info.uid; - AccessPreference preference = (AccessPreference) getCachedPreference(key); - if (preference == null) { - preference = new AccessPreference(getPrefContext(), entry); - preference.setKey(key); - preference.setOnPreferenceChangeListener(this); - getPreferenceScreen().addPreference(preference); - } else { - preference.setDisabledByAdmin(checkIfMeteredDataRestricted(getContext(), - entry.info.packageName, UserHandle.getUserId(entry.info.uid))); - preference.reuse(); - } - preference.setOrder(i); - } - setLoading(false, true); - removeCachedPrefs(getPreferenceScreen()); - } - - @Override - public void onPackageIconChanged() { - - } - - @Override - public void onPackageSizeChanged(String packageName) { - - } - - @Override - public void onAllSizesComputed() { - - } - - @Override - public void onLauncherInfoChanged() { - - } - - @Override - public void onLoadEntriesCompleted() { - + protected String getLogTag() { + return TAG; } @Override @@ -217,173 +108,4 @@ public class UnrestrictedDataAccess extends SettingsPreferenceFragment protected int getPreferenceScreenResId() { return R.xml.unrestricted_data_access_settings; } - - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - if (preference instanceof AccessPreference) { - AccessPreference accessPreference = (AccessPreference) preference; - boolean whitelisted = newValue == Boolean.TRUE; - logSpecialPermissionChange(whitelisted, accessPreference.mEntry.info.packageName); - mDataSaverBackend.setIsWhitelisted(accessPreference.mEntry.info.uid, - accessPreference.mEntry.info.packageName, whitelisted); - accessPreference.mState.isDataSaverWhitelisted = whitelisted; - return true; - } - return false; - } - - @VisibleForTesting - void logSpecialPermissionChange(boolean whitelisted, String packageName) { - int logCategory = whitelisted ? MetricsEvent.APP_SPECIAL_PERMISSION_UNL_DATA_ALLOW - : MetricsEvent.APP_SPECIAL_PERMISSION_UNL_DATA_DENY; - FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider().action(getContext(), - logCategory, packageName); - } - - @VisibleForTesting - boolean shouldAddPreference(AppEntry app) { - return app != null && UserHandle.isApp(app.info.uid); - } - - @VisibleForTesting - class AccessPreference extends AppSwitchPreference - implements DataSaverBackend.Listener { - private final AppEntry mEntry; - private final DataUsageState mState; - private final RestrictedPreferenceHelper mHelper; - - public AccessPreference(final Context context, AppEntry entry) { - super(context); - setWidgetLayoutResource(R.layout.restricted_switch_widget); - mHelper = new RestrictedPreferenceHelper(context, this, null); - mEntry = entry; - mState = (DataUsageState) mEntry.extraInfo; - mEntry.ensureLabel(getContext()); - setDisabledByAdmin(checkIfMeteredDataRestricted(context, entry.info.packageName, - UserHandle.getUserId(entry.info.uid))); - setState(); - if (mEntry.icon != null) { - setIcon(mEntry.icon); - } - } - - @Override - public void onAttached() { - super.onAttached(); - mDataSaverBackend.addListener(this); - } - - @Override - public void onDetached() { - mDataSaverBackend.remListener(this); - super.onDetached(); - } - - @Override - protected void onClick() { - if (mState.isDataSaverBlacklisted) { - // app is blacklisted, launch App Data Usage screen - AppInfoDashboardFragment.startAppInfoFragment(AppDataUsage.class, - R.string.app_data_usage, - null /* arguments */, - UnrestrictedDataAccess.this, - mEntry); - } else { - // app is not blacklisted, let superclass handle toggle switch - super.onClick(); - } - } - - @Override - public void performClick() { - if (!mHelper.performClick()) { - super.performClick(); - } - } - - // Sets UI state based on whitelist/blacklist status. - private void setState() { - setTitle(mEntry.label); - if (mState != null) { - setChecked(mState.isDataSaverWhitelisted); - if (isDisabledByAdmin()) { - setSummary(R.string.disabled_by_admin); - } else if (mState.isDataSaverBlacklisted) { - setSummary(R.string.restrict_background_blacklisted); - } else { - setSummary(""); - } - } - } - - public void reuse() { - setState(); - notifyChanged(); - } - - @Override - public void onBindViewHolder(PreferenceViewHolder holder) { - if (mEntry.icon == null) { - holder.itemView.post(new Runnable() { - @Override - public void run() { - // Ensure we have an icon before binding. - mApplicationsState.ensureIcon(mEntry); - // This might trigger us to bind again, but it gives an easy way to only - // load the icon once its needed, so its probably worth it. - setIcon(mEntry.icon); - } - }); - } - final boolean disabledByAdmin = isDisabledByAdmin(); - final View widgetFrame = holder.findViewById(android.R.id.widget_frame); - if (disabledByAdmin) { - widgetFrame.setVisibility(View.VISIBLE); - } else { - widgetFrame.setVisibility(mState != null && mState.isDataSaverBlacklisted - ? View.INVISIBLE : View.VISIBLE); - } - super.onBindViewHolder(holder); - - mHelper.onBindViewHolder(holder); - holder.findViewById(R.id.restricted_icon).setVisibility( - disabledByAdmin ? View.VISIBLE : View.GONE); - holder.findViewById(android.R.id.switch_widget).setVisibility( - disabledByAdmin ? View.GONE : View.VISIBLE); - } - - @Override - public void onDataSaverChanged(boolean isDataSaving) { - } - - @Override - public void onWhitelistStatusChanged(int uid, boolean isWhitelisted) { - if (mState != null && mEntry.info.uid == uid) { - mState.isDataSaverWhitelisted = isWhitelisted; - reuse(); - } - } - - @Override - public void onBlacklistStatusChanged(int uid, boolean isBlacklisted) { - if (mState != null && mEntry.info.uid == uid) { - mState.isDataSaverBlacklisted = isBlacklisted; - reuse(); - } - } - - public void setDisabledByAdmin(EnforcedAdmin admin) { - mHelper.setDisabledByAdmin(admin); - } - - public boolean isDisabledByAdmin() { - return mHelper.isDisabledByAdmin(); - } - - @VisibleForTesting - public AppEntry getEntryForTest() { - return mEntry; - } - } - } diff --git a/src/com/android/settings/datausage/UnrestrictedDataAccessPreference.java b/src/com/android/settings/datausage/UnrestrictedDataAccessPreference.java new file mode 100644 index 00000000000..0c462db9558 --- /dev/null +++ b/src/com/android/settings/datausage/UnrestrictedDataAccessPreference.java @@ -0,0 +1,185 @@ +/* + * 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.datausage; + +import static com.android.settingslib.RestrictedLockUtils.checkIfMeteredDataRestricted; + +import android.content.Context; +import android.os.UserHandle; +import android.view.View; + +import com.android.settings.R; +import com.android.settings.applications.appinfo.AppInfoDashboardFragment; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.widget.AppSwitchPreference; +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; +import com.android.settingslib.RestrictedPreferenceHelper; +import com.android.settingslib.applications.ApplicationsState; +import com.android.settingslib.applications.ApplicationsState.AppEntry; + +import androidx.preference.PreferenceViewHolder; + +public class UnrestrictedDataAccessPreference extends AppSwitchPreference implements + DataSaverBackend.Listener { + + private final ApplicationsState mApplicationsState; + private final AppEntry mEntry; + private final AppStateDataUsageBridge.DataUsageState mDataUsageState; + private final DataSaverBackend mDataSaverBackend; + private final DashboardFragment mParentFragment; + private final RestrictedPreferenceHelper mHelper; + + public UnrestrictedDataAccessPreference(final Context context, AppEntry entry, + ApplicationsState applicationsState, DataSaverBackend dataSaverBackend, + DashboardFragment parentFragment) { + super(context); + setWidgetLayoutResource(R.layout.restricted_switch_widget); + mHelper = new RestrictedPreferenceHelper(context, this, null); + mEntry = entry; + mDataUsageState = (AppStateDataUsageBridge.DataUsageState) mEntry.extraInfo; + mEntry.ensureLabel(context); + mApplicationsState = applicationsState; + mDataSaverBackend = dataSaverBackend; + mParentFragment = parentFragment; + setDisabledByAdmin(checkIfMeteredDataRestricted(context, entry.info.packageName, + UserHandle.getUserId(entry.info.uid))); + updateState(); + setKey(generateKey(mEntry)); + if (mEntry.icon != null) { + setIcon(mEntry.icon); + } + } + + static String generateKey(final AppEntry entry) { + return entry.info.packageName + "|" + entry.info.uid; + } + + @Override + public void onAttached() { + super.onAttached(); + mDataSaverBackend.addListener(this); + } + + @Override + public void onDetached() { + mDataSaverBackend.remListener(this); + super.onDetached(); + } + + @Override + protected void onClick() { + if (mDataUsageState.isDataSaverBlacklisted) { + // app is blacklisted, launch App Data Usage screen + AppInfoDashboardFragment.startAppInfoFragment(AppDataUsage.class, + R.string.app_data_usage, + null /* arguments */, + mParentFragment, + mEntry); + } else { + // app is not blacklisted, let superclass handle toggle switch + super.onClick(); + } + } + + @Override + public void performClick() { + if (!mHelper.performClick()) { + super.performClick(); + } + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + if (mEntry.icon == null) { + holder.itemView.post(new Runnable() { + @Override + public void run() { + // Ensure we have an icon before binding. + mApplicationsState.ensureIcon(mEntry); + // This might trigger us to bind again, but it gives an easy way to only + // load the icon once its needed, so its probably worth it. + setIcon(mEntry.icon); + } + }); + } + final boolean disabledByAdmin = isDisabledByAdmin(); + final View widgetFrame = holder.findViewById(android.R.id.widget_frame); + if (disabledByAdmin) { + widgetFrame.setVisibility(View.VISIBLE); + } else { + widgetFrame.setVisibility( + mDataUsageState != null && mDataUsageState.isDataSaverBlacklisted + ? View.INVISIBLE : View.VISIBLE); + } + super.onBindViewHolder(holder); + + mHelper.onBindViewHolder(holder); + holder.findViewById(R.id.restricted_icon).setVisibility( + disabledByAdmin ? View.VISIBLE : View.GONE); + holder.findViewById(android.R.id.switch_widget).setVisibility( + disabledByAdmin ? View.GONE : View.VISIBLE); + } + + @Override + public void onDataSaverChanged(boolean isDataSaving) { + } + + @Override + public void onWhitelistStatusChanged(int uid, boolean isWhitelisted) { + if (mDataUsageState != null && mEntry.info.uid == uid) { + mDataUsageState.isDataSaverWhitelisted = isWhitelisted; + updateState(); + } + } + + @Override + public void onBlacklistStatusChanged(int uid, boolean isBlacklisted) { + if (mDataUsageState != null && mEntry.info.uid == uid) { + mDataUsageState.isDataSaverBlacklisted = isBlacklisted; + updateState(); + } + } + + public AppStateDataUsageBridge.DataUsageState getDataUsageState() { + return mDataUsageState; + } + + public AppEntry getEntry() { + return mEntry; + } + + public boolean isDisabledByAdmin() { + return mHelper.isDisabledByAdmin(); + } + + public void setDisabledByAdmin(EnforcedAdmin admin) { + mHelper.setDisabledByAdmin(admin); + } + + // Sets UI state based on whitelist/blacklist status. + public void updateState() { + setTitle(mEntry.label); + if (mDataUsageState != null) { + setChecked(mDataUsageState.isDataSaverWhitelisted); + if (isDisabledByAdmin()) { + setSummary(R.string.disabled_by_admin); + } else if (mDataUsageState.isDataSaverBlacklisted) { + setSummary(R.string.restrict_background_blacklisted); + } else { + setSummary(""); + } + } + notifyChanged(); + } +} diff --git a/src/com/android/settings/datausage/UnrestrictedDataAccessPreferenceController.java b/src/com/android/settings/datausage/UnrestrictedDataAccessPreferenceController.java new file mode 100644 index 00000000000..dc6b7feb4af --- /dev/null +++ b/src/com/android/settings/datausage/UnrestrictedDataAccessPreferenceController.java @@ -0,0 +1,235 @@ +/* + * 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.datausage; + +import static com.android.settingslib.RestrictedLockUtils.checkIfMeteredDataRestricted; + +import android.app.Application; +import android.content.Context; +import android.os.UserHandle; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settings.applications.AppStateBaseBridge; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.applications.ApplicationsState; +import com.android.settingslib.applications.ApplicationsState.AppEntry; +import com.android.settingslib.applications.ApplicationsState.AppFilter; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnDestroy; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; + +import java.util.ArrayList; +import java.util.Set; +import java.util.TreeSet; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + + +public class UnrestrictedDataAccessPreferenceController extends BasePreferenceController implements + LifecycleObserver, OnStart, OnStop, OnDestroy, ApplicationsState.Callbacks, + AppStateBaseBridge.Callback, Preference.OnPreferenceChangeListener { + + private final ApplicationsState mApplicationsState; + private final AppStateDataUsageBridge mDataUsageBridge; + private final DataSaverBackend mDataSaverBackend; + private ApplicationsState.Session mSession; + private AppFilter mFilter; + private DashboardFragment mParentFragment; + private PreferenceScreen mScreen; + private boolean mExtraLoaded; + + public UnrestrictedDataAccessPreferenceController(Context context, String key) { + super(context, key); + mApplicationsState = ApplicationsState.getInstance( + (Application) context.getApplicationContext()); + mDataSaverBackend = new DataSaverBackend(context); + mDataUsageBridge = new AppStateDataUsageBridge(mApplicationsState, this, mDataSaverBackend); + } + + public void setFilter(AppFilter filter) { + mFilter = filter; + } + + public void setParentFragment(DashboardFragment parentFragment) { + mParentFragment = parentFragment; + } + + public void setSession(Lifecycle lifecycle) { + mSession = mApplicationsState.newSession(this, lifecycle); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mScreen = screen; + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public void onStart() { + mDataUsageBridge.resume(); + } + + @Override + public void onStop() { + mDataUsageBridge.pause(); + } + + @Override + public void onDestroy() { + mDataUsageBridge.release(); + } + + @Override + public void onExtraInfoUpdated() { + mExtraLoaded = true; + rebuild(); + } + + @Override + public void onRunningStateChanged(boolean running) { + + } + + @Override + public void onPackageListChanged() { + + } + + @Override + public void onRebuildComplete(ArrayList apps) { + if (apps == null) { + return; + } + + // Create apps key set for removing useless preferences + final Set appsKeySet = new TreeSet<>(); + // Add or update preferences + final int N = apps.size(); + for (int i = 0; i < N; i++) { + final AppEntry entry = apps.get(i); + if (!shouldAddPreference(entry)) { + continue; + } + final String prefkey = UnrestrictedDataAccessPreference.generateKey(entry); + appsKeySet.add(prefkey); + UnrestrictedDataAccessPreference preference = + (UnrestrictedDataAccessPreference) mScreen.findPreference(prefkey); + if (preference == null) { + preference = new UnrestrictedDataAccessPreference(mScreen.getContext(), entry, + mApplicationsState, mDataSaverBackend, mParentFragment); + preference.setOnPreferenceChangeListener(this); + mScreen.addPreference(preference); + } else { + preference.setDisabledByAdmin(checkIfMeteredDataRestricted(mContext, + entry.info.packageName, UserHandle.getUserId(entry.info.uid))); + preference.updateState(); + } + preference.setOrder(i); + } + + // Remove useless preferences + removeUselessPrefs(appsKeySet); + } + + @Override + public void onPackageIconChanged() { + + } + + @Override + public void onPackageSizeChanged(String packageName) { + + } + + @Override + public void onAllSizesComputed() { + + } + + @Override + public void onLauncherInfoChanged() { + + } + + @Override + public void onLoadEntriesCompleted() { + + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (preference instanceof UnrestrictedDataAccessPreference) { + final UnrestrictedDataAccessPreference + accessPreference = (UnrestrictedDataAccessPreference) preference; + boolean whitelisted = newValue == Boolean.TRUE; + logSpecialPermissionChange(whitelisted, accessPreference.getEntry().info.packageName); + mDataSaverBackend.setIsWhitelisted(accessPreference.getEntry().info.uid, + accessPreference.getEntry().info.packageName, whitelisted); + accessPreference.getDataUsageState().isDataSaverWhitelisted = whitelisted; + return true; + } + return false; + } + + public void rebuild() { + if (!mExtraLoaded) { + return; + } + + final ArrayList apps = mSession.rebuild(mFilter, + ApplicationsState.ALPHA_COMPARATOR); + if (apps != null) { + onRebuildComplete(apps); + } + } + + private void removeUselessPrefs(final Set appsKeySet) { + final int prefCount = mScreen.getPreferenceCount(); + String prefKey; + if (prefCount > 0) { + for (int i = prefCount - 1; i >= 0; i--) { + Preference pref = mScreen.getPreference(i); + prefKey = pref.getKey(); + if (!appsKeySet.isEmpty() && appsKeySet.contains(prefKey)) { + continue; + } + mScreen.removePreference(pref); + } + } + } + + @VisibleForTesting + void logSpecialPermissionChange(boolean whitelisted, String packageName) { + final int logCategory = whitelisted ? MetricsEvent.APP_SPECIAL_PERMISSION_UNL_DATA_ALLOW + : MetricsEvent.APP_SPECIAL_PERMISSION_UNL_DATA_DENY; + FeatureFactory.getFactory(mContext).getMetricsFeatureProvider().action(mContext, + logCategory, packageName); + } + + @VisibleForTesting + static boolean shouldAddPreference(AppEntry app) { + return app != null && UserHandle.isApp(app.info.uid); + } +} diff --git a/tests/robotests/assets/grandfather_not_implementing_index_provider b/tests/robotests/assets/grandfather_not_implementing_index_provider index 8bd4f823840..57c3e3dacd7 100644 --- a/tests/robotests/assets/grandfather_not_implementing_index_provider +++ b/tests/robotests/assets/grandfather_not_implementing_index_provider @@ -1,4 +1,5 @@ com.android.settings.applications.appinfo.AppInfoDashboardFragment +com.android.settings.datausage.UnrestrictedDataAccess com.android.settings.bluetooth.DevicePickerFragment com.android.settings.bluetooth.BluetoothDeviceDetailsFragment com.android.settings.bluetooth.BluetoothPairingDetail diff --git a/tests/robotests/assets/grandfather_not_implementing_indexable b/tests/robotests/assets/grandfather_not_implementing_indexable index ccb6f920c23..442df76f909 100644 --- a/tests/robotests/assets/grandfather_not_implementing_indexable +++ b/tests/robotests/assets/grandfather_not_implementing_indexable @@ -30,7 +30,6 @@ com.android.settings.notification.RedactionInterstitial$RedactionInterstitialFra com.android.settings.applications.appinfo.DrawOverlayDetails com.android.settings.backup.ToggleBackupSettingFragment com.android.settings.users.UserDetailsSettings -com.android.settings.datausage.UnrestrictedDataAccess com.android.settings.accessibility.ToggleScreenMagnificationPreferenceFragmentForSetupWizard com.android.settings.fuelgauge.BatteryHistoryDetail com.android.settings.applications.RunningServices diff --git a/tests/robotests/src/com/android/settings/datausage/UnrestrictedDataAccessTest.java b/tests/robotests/src/com/android/settings/datausage/UnrestrictedDataAccessPreferenceControllerTest.java similarity index 62% rename from tests/robotests/src/com/android/settings/datausage/UnrestrictedDataAccessTest.java rename to tests/robotests/src/com/android/settings/datausage/UnrestrictedDataAccessPreferenceControllerTest.java index 01639d40c0e..69f4e0f32ef 100644 --- a/tests/robotests/src/com/android/settings/datausage/UnrestrictedDataAccessTest.java +++ b/tests/robotests/src/com/android/settings/datausage/UnrestrictedDataAccessPreferenceControllerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * 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. @@ -23,6 +23,7 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -35,11 +36,12 @@ import androidx.preference.PreferenceScreen; import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; +import com.android.settings.applications.AppStateBaseBridge; import com.android.settings.datausage.AppStateDataUsageBridge.DataUsageState; -import com.android.settings.datausage.UnrestrictedDataAccess.AccessPreference; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.ShadowRestrictedLockUtils; +import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.applications.ApplicationsState.AppEntry; import org.junit.Before; @@ -49,77 +51,85 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import org.robolectric.annotation.Implements; import org.robolectric.util.ReflectionHelpers; import java.util.ArrayList; @RunWith(SettingsRobolectricTestRunner.class) -@Config(shadows = ShadowRestrictedLockUtils.class) -public class UnrestrictedDataAccessTest { +@Config(shadows = { + ShadowRestrictedLockUtils.class, + UnrestrictedDataAccessPreferenceControllerTest.ShadowAppStateBaseBridge.class +}) +public class UnrestrictedDataAccessPreferenceControllerTest { + @Mock + private ApplicationsState mState; + @Mock + private ApplicationsState.Session mSession; - @Mock - private AppEntry mAppEntry; - private UnrestrictedDataAccess mFragment; + private Context mContext; private FakeFeatureFactory mFeatureFactory; - @Mock - private PreferenceScreen mPreferenceScreen; - @Mock private PreferenceManager mPreferenceManager; - @Mock - private DataSaverBackend mDataSaverBackend; + private PreferenceScreen mPreferenceScreen; + private UnrestrictedDataAccess mFragment; + private UnrestrictedDataAccessPreferenceController mController; @Before public void setUp() { MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; mFeatureFactory = FakeFeatureFactory.setupForTest(); - mFragment = spy(new UnrestrictedDataAccess()); + ReflectionHelpers.setStaticField(ApplicationsState.class, "sInstance", mState); + when(mState.newSession(any())).thenReturn(mSession); + mController = spy(new UnrestrictedDataAccessPreferenceController(mContext, "pref_key")); } @Test - public void testShouldAddPreferenceForApps() { - mAppEntry.info = new ApplicationInfo(); - mAppEntry.info.uid = Process.FIRST_APPLICATION_UID + 10; - - assertThat(mFragment.shouldAddPreference(mAppEntry)).isTrue(); + public void shouldAddPreference_forApps_shouldBeTrue() { + final int uid = Process.FIRST_APPLICATION_UID + 10; + final AppEntry entry = createAppEntry(uid); + assertThat(UnrestrictedDataAccessPreferenceController.shouldAddPreference(entry)).isTrue(); } @Test - public void testShouldNotAddPreferenceForNonApps() { - mAppEntry.info = new ApplicationInfo(); - mAppEntry.info.uid = Process.FIRST_APPLICATION_UID - 10; - - assertThat(mFragment.shouldAddPreference(mAppEntry)).isFalse(); + public void shouldAddPreference_forNonApps_shouldBeFalse() { + final int uid = Process.FIRST_APPLICATION_UID - 10; + final AppEntry entry = createAppEntry(uid); + assertThat(UnrestrictedDataAccessPreferenceController.shouldAddPreference(entry)).isFalse(); } @Test public void logSpecialPermissionChange() { - mFragment.logSpecialPermissionChange(true, "app"); + mController.logSpecialPermissionChange(true, "app"); verify(mFeatureFactory.metricsFeatureProvider).action(nullable(Context.class), eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_UNL_DATA_ALLOW), eq("app")); - mFragment.logSpecialPermissionChange(false, "app"); + mController.logSpecialPermissionChange(false, "app"); verify(mFeatureFactory.metricsFeatureProvider).action(nullable(Context.class), eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_UNL_DATA_DENY), eq("app")); } @Test - public void testOnRebuildComplete_restricted_shouldBeDisabled() { - final Context context = RuntimeEnvironment.application; - doReturn(context).when(mFragment).getContext(); - when(mPreferenceManager.getContext()).thenReturn(context); - doReturn(true).when(mFragment).shouldAddPreference(any(AppEntry.class)); + public void onRebuildComplete_restricted_shouldBeDisabled() { + mFragment = spy(new UnrestrictedDataAccess()); doNothing().when(mFragment).setLoading(anyBoolean(), anyBoolean()); + mController.setParentFragment(mFragment); + mPreferenceManager = new PreferenceManager(mContext); + mPreferenceScreen = spy(mPreferenceManager.createPreferenceScreen(mContext)); + doReturn(mPreferenceManager).when(mFragment).getPreferenceManager(); doReturn(mPreferenceScreen).when(mFragment).getPreferenceScreen(); - when(mFragment.getPreferenceManager()).thenReturn(mPreferenceManager); - ReflectionHelpers.setField(mFragment, "mDataSaverBackend", mDataSaverBackend); + doReturn(0).when(mPreferenceScreen).getPreferenceCount(); + final DataSaverBackend dataSaverBackend = mock(DataSaverBackend.class); + ReflectionHelpers.setField(mController, "mDataSaverBackend", dataSaverBackend); + ReflectionHelpers.setField(mController, "mScreen", mPreferenceScreen); final String testPkg1 = "com.example.one"; final String testPkg2 = "com.example.two"; ShadowRestrictedLockUtils.setRestrictedPkgs(testPkg2); doAnswer((invocation) -> { - final AccessPreference preference = invocation.getArgument(0); - final AppEntry entry = preference.getEntryForTest(); + final UnrestrictedDataAccessPreference preference = invocation.getArgument(0); + final AppEntry entry = preference.getEntry(); // Verify preference is disabled by admin and the summary is changed accordingly. if (testPkg1.equals(entry.info.packageName)) { assertThat(preference.isDisabledByAdmin()).isFalse(); @@ -127,7 +137,7 @@ public class UnrestrictedDataAccessTest { } else if (testPkg2.equals(entry.info.packageName)) { assertThat(preference.isDisabledByAdmin()).isTrue(); assertThat(preference.getSummary()).isEqualTo( - context.getString(R.string.disabled_by_admin)); + mContext.getString(R.string.disabled_by_admin)); } assertThat(preference.isChecked()).isFalse(); preference.performClick(); @@ -144,8 +154,9 @@ public class UnrestrictedDataAccessTest { } ShadowRestrictedLockUtils.clearAdminSupportDetailsIntentLaunch(); return null; - }).when(mPreferenceScreen).addPreference(any(AccessPreference.class)); - mFragment.onRebuildComplete(createAppEntries(testPkg1, testPkg2)); + }).when(mPreferenceScreen).addPreference(any(UnrestrictedDataAccessPreference.class)); + + mController.onRebuildComplete(createAppEntries(testPkg1, testPkg2)); } private ArrayList createAppEntries(String... packageNames) { @@ -155,7 +166,7 @@ public class UnrestrictedDataAccessTest { info.packageName = packageNames[i]; info.uid = Process.FIRST_APPLICATION_UID + i; info.sourceDir = info.packageName; - final AppEntry appEntry = spy(new AppEntry(RuntimeEnvironment.application, info, i)); + final AppEntry appEntry = spy(new AppEntry(mContext, info, i)); appEntry.extraInfo = new DataUsageState(false, false); doNothing().when(appEntry).ensureLabel(any(Context.class)); ReflectionHelpers.setField(appEntry, "info", info); @@ -163,4 +174,20 @@ public class UnrestrictedDataAccessTest { } return appEntries; } + + private AppEntry createAppEntry(int uid) { + final ApplicationInfo info = new ApplicationInfo(); + info.packageName = "com.example.three"; + info.uid = uid; + info.sourceDir = info.packageName; + return new AppEntry(mContext, info, uid); + } + + @Implements(AppStateBaseBridge.class) + public static class ShadowAppStateBaseBridge { + + public void __constructor__(ApplicationsState appState, + AppStateBaseBridge.Callback callback) { + } + } }