* Rename variables/arguments to clarify that we are working with policy flags, meaning that there can be multiple policies described by a uid policy, not just one. * When checking policies for a policy flag, do a proper flag check, rather than a direct comparison that assumes one single policy. * Only alter the displayed setting for the "Background network access" and "Unrestricted mobile data usage" toggles in response to underlying policy changes, to ensure their state represents the current reality. * Add a couple explanatory comments. Test: Manual: Install an app that does not have INTERNET permission. Open the "Unrestricted mobile data" page of Settings. Try to activate the toggle for such an app. The toggle should activate successfully without needing to be tapped twice. Issue: calyxos#2547 Change-Id: I9f2f028be4a21158a68c60982253d85586f60cdb
266 lines
9.8 KiB
Java
266 lines
9.8 KiB
Java
/*
|
|
* Copyright (C) 2016 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 android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND;
|
|
import static android.net.NetworkPolicyManager.POLICY_NONE;
|
|
import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
|
|
|
|
import android.app.settings.SettingsEnums;
|
|
import android.content.Context;
|
|
import android.net.NetworkPolicyManager;
|
|
import android.util.SparseIntArray;
|
|
|
|
import com.android.settings.overlay.FeatureFactory;
|
|
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
|
|
import com.android.settingslib.utils.ThreadUtils;
|
|
|
|
import org.jetbrains.annotations.NotNull;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
public class DataSaverBackend {
|
|
|
|
private static final String TAG = "DataSaverBackend";
|
|
|
|
private final Context mContext;
|
|
private final MetricsFeatureProvider mMetricsFeatureProvider;
|
|
|
|
private final NetworkPolicyManager mPolicyManager;
|
|
private final ArrayList<Listener> mListeners = new ArrayList<>();
|
|
private SparseIntArray mUidPolicies = new SparseIntArray();
|
|
private boolean mAllowlistInitialized;
|
|
private boolean mDenylistInitialized;
|
|
|
|
// TODO: Staticize into only one.
|
|
public DataSaverBackend(@NotNull Context context) {
|
|
// TODO(b/246537614):Use fragment context to DataSaverBackend class will caused memory leak
|
|
mContext = context.getApplicationContext();
|
|
mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
|
|
mPolicyManager = NetworkPolicyManager.from(mContext);
|
|
}
|
|
|
|
public void addListener(Listener listener) {
|
|
mListeners.add(listener);
|
|
if (mListeners.size() == 1) {
|
|
mPolicyManager.registerListener(mPolicyListener);
|
|
}
|
|
listener.onDataSaverChanged(isDataSaverEnabled());
|
|
}
|
|
|
|
public void remListener(Listener listener) {
|
|
mListeners.remove(listener);
|
|
if (mListeners.size() == 0) {
|
|
mPolicyManager.unregisterListener(mPolicyListener);
|
|
}
|
|
}
|
|
|
|
public boolean isDataSaverEnabled() {
|
|
return mPolicyManager.getRestrictBackground();
|
|
}
|
|
|
|
public void setDataSaverEnabled(boolean enabled) {
|
|
mPolicyManager.setRestrictBackground(enabled);
|
|
mMetricsFeatureProvider.action(
|
|
mContext, SettingsEnums.ACTION_DATA_SAVER_MODE, enabled ? 1 : 0);
|
|
}
|
|
|
|
public void refreshAllowlist() {
|
|
loadAllowlist();
|
|
}
|
|
|
|
public void setIsAllowlisted(int uid, String packageName, boolean allowlisted) {
|
|
setUidPolicyFlag(uid, POLICY_ALLOW_METERED_BACKGROUND, allowlisted);
|
|
if (allowlisted) {
|
|
mPolicyManager.addUidPolicy(uid, POLICY_ALLOW_METERED_BACKGROUND);
|
|
mMetricsFeatureProvider.action(
|
|
mContext, SettingsEnums.ACTION_DATA_SAVER_WHITELIST, packageName);
|
|
} else {
|
|
mPolicyManager.removeUidPolicy(uid, POLICY_ALLOW_METERED_BACKGROUND);
|
|
}
|
|
mPolicyManager.removeUidPolicy(uid, POLICY_REJECT_METERED_BACKGROUND);
|
|
}
|
|
|
|
public boolean isAllowlisted(int uid) {
|
|
loadAllowlist();
|
|
return isUidPolicyFlagSet(uid, POLICY_ALLOW_METERED_BACKGROUND);
|
|
}
|
|
|
|
private void loadAllowlist() {
|
|
if (mAllowlistInitialized) {
|
|
return;
|
|
}
|
|
loadUidPolicies(POLICY_ALLOW_METERED_BACKGROUND);
|
|
mAllowlistInitialized = true;
|
|
}
|
|
|
|
public void refreshDenylist() {
|
|
loadDenylist();
|
|
}
|
|
|
|
public void setIsDenylisted(int uid, String packageName, boolean denylisted) {
|
|
setUidPolicyFlag(uid, POLICY_REJECT_METERED_BACKGROUND, denylisted);
|
|
if (denylisted) {
|
|
mPolicyManager.addUidPolicy(uid, POLICY_REJECT_METERED_BACKGROUND);
|
|
mMetricsFeatureProvider.action(
|
|
mContext, SettingsEnums.ACTION_DATA_SAVER_BLACKLIST, packageName);
|
|
} else {
|
|
mPolicyManager.removeUidPolicy(uid, POLICY_REJECT_METERED_BACKGROUND);
|
|
}
|
|
mPolicyManager.removeUidPolicy(uid, POLICY_ALLOW_METERED_BACKGROUND);
|
|
}
|
|
|
|
private void loadUidPolicies(int policyFlag) {
|
|
final int[] uidsWithPolicyArray = mPolicyManager.getUidsWithPolicy(policyFlag);
|
|
final ArrayList<Integer> uidsWithPolicyFlag = new ArrayList<>(uidsWithPolicyArray.length);
|
|
// Convert from int[] to a list of Integer.
|
|
for (final int uid : uidsWithPolicyArray) {
|
|
uidsWithPolicyFlag.add(uid);
|
|
}
|
|
// Update existing cached UID policies.
|
|
for (int i = 0; i < mUidPolicies.size(); i++) {
|
|
final Integer cachedEntryUid = mUidPolicies.keyAt(i);
|
|
if (uidsWithPolicyFlag.remove(cachedEntryUid)) {
|
|
// UID had the policy. It was removed so we don't have to process it twice.
|
|
setCachedUidPolicyFlagAt(i, policyFlag, true);
|
|
} else {
|
|
// UID does not have the policy.
|
|
setCachedUidPolicyFlagAt(i, policyFlag, false);
|
|
}
|
|
}
|
|
// Add policies for remaining UIDs, which did not have cached policies, so we're it.
|
|
for (final int uid : uidsWithPolicyFlag) {
|
|
mUidPolicies.put(uid, policyFlag);
|
|
}
|
|
}
|
|
|
|
private void setCachedUidPolicyFlag(int uid, int policyFlag, boolean add) {
|
|
final int index = mUidPolicies.indexOfKey(uid);
|
|
if (index < 0) {
|
|
if (add) {
|
|
mUidPolicies.put(uid, policyFlag);
|
|
}
|
|
return;
|
|
}
|
|
setCachedUidPolicyFlagAt(index, policyFlag, add);
|
|
}
|
|
|
|
private void setCachedUidPolicyFlagAt(int index, int policyFlag, boolean add) {
|
|
final int currentPolicy = mUidPolicies.valueAt(index);
|
|
final int newPolicy = add ? (currentPolicy | policyFlag) : (currentPolicy & ~policyFlag);
|
|
mUidPolicies.setValueAt(index, newPolicy);
|
|
}
|
|
|
|
private void setUidPolicyFlag(int uid, int policyFlag, boolean add) {
|
|
if (add) {
|
|
mPolicyManager.addUidPolicy(uid, policyFlag);
|
|
} else {
|
|
mPolicyManager.removeUidPolicy(uid, policyFlag);
|
|
}
|
|
setCachedUidPolicyFlag(uid, policyFlag, add);
|
|
}
|
|
|
|
private boolean isUidPolicyFlagSet(int uid, int policyFlag) {
|
|
return isFlagSet(mUidPolicies.get(uid, POLICY_NONE), policyFlag);
|
|
}
|
|
|
|
private static boolean isFlagSet(int overallPolicy, int policyFlag) {
|
|
return (overallPolicy & policyFlag) == policyFlag;
|
|
}
|
|
|
|
public boolean isDenylisted(int uid) {
|
|
loadDenylist();
|
|
return isUidPolicyFlagSet(uid, POLICY_REJECT_METERED_BACKGROUND);
|
|
}
|
|
|
|
private void loadDenylist() {
|
|
if (mDenylistInitialized) {
|
|
return;
|
|
}
|
|
loadUidPolicies(POLICY_REJECT_METERED_BACKGROUND);
|
|
mDenylistInitialized = true;
|
|
}
|
|
|
|
private void handleRestrictBackgroundChanged(boolean isDataSaving) {
|
|
for (int i = 0; i < mListeners.size(); i++) {
|
|
mListeners.get(i).onDataSaverChanged(isDataSaving);
|
|
}
|
|
}
|
|
|
|
private void handleAllowlistChanged(int uid, boolean isAllowlisted) {
|
|
for (int i = 0; i < mListeners.size(); i++) {
|
|
mListeners.get(i).onAllowlistStatusChanged(uid, isAllowlisted);
|
|
}
|
|
}
|
|
|
|
private void handleDenylistChanged(int uid, boolean isDenylisted) {
|
|
for (int i = 0; i < mListeners.size(); i++) {
|
|
mListeners.get(i).onDenylistStatusChanged(uid, isDenylisted);
|
|
}
|
|
}
|
|
|
|
private void handleUidPoliciesChanged(int uid, int newPolicy) {
|
|
loadAllowlist();
|
|
loadDenylist();
|
|
|
|
// We only care about allow/reject metered background policy here.
|
|
newPolicy &= POLICY_ALLOW_METERED_BACKGROUND | POLICY_REJECT_METERED_BACKGROUND;
|
|
|
|
final int oldPolicy = mUidPolicies.get(uid, POLICY_NONE);
|
|
if (newPolicy == POLICY_NONE) {
|
|
mUidPolicies.delete(uid);
|
|
} else {
|
|
mUidPolicies.put(uid, newPolicy);
|
|
}
|
|
|
|
final boolean wasAllowlisted = isFlagSet(oldPolicy, POLICY_ALLOW_METERED_BACKGROUND);
|
|
final boolean wasDenylisted = isFlagSet(oldPolicy, POLICY_REJECT_METERED_BACKGROUND);
|
|
final boolean isAllowlisted = isFlagSet(newPolicy, POLICY_ALLOW_METERED_BACKGROUND);
|
|
final boolean isDenylisted = isFlagSet(newPolicy, POLICY_REJECT_METERED_BACKGROUND);
|
|
|
|
if (wasAllowlisted != isAllowlisted) {
|
|
handleAllowlistChanged(uid, isAllowlisted);
|
|
}
|
|
|
|
if (wasDenylisted != isDenylisted) {
|
|
handleDenylistChanged(uid, isDenylisted);
|
|
}
|
|
|
|
}
|
|
|
|
private final NetworkPolicyManager.Listener mPolicyListener =
|
|
new NetworkPolicyManager.Listener() {
|
|
@Override
|
|
public void onUidPoliciesChanged(final int uid, final int uidPolicies) {
|
|
ThreadUtils.postOnMainThread(() -> handleUidPoliciesChanged(uid, uidPolicies));
|
|
}
|
|
|
|
@Override
|
|
public void onRestrictBackgroundChanged(final boolean isDataSaving) {
|
|
ThreadUtils.postOnMainThread(() -> handleRestrictBackgroundChanged(isDataSaving));
|
|
}
|
|
};
|
|
|
|
public interface Listener {
|
|
void onDataSaverChanged(boolean isDataSaving);
|
|
|
|
/** This is called when allow list status is changed. */
|
|
default void onAllowlistStatusChanged(int uid, boolean isAllowlisted) {}
|
|
|
|
/** This is called when deny list status is changed. */
|
|
default void onDenylistStatusChanged(int uid, boolean isDenylisted) {}
|
|
}
|
|
}
|