1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.fuelgauge.datasaver; 18 19 import static android.net.NetworkPolicyManager.POLICY_NONE; 20 import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; 21 22 import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME; 23 24 import android.content.Context; 25 import android.content.SharedPreferences; 26 import android.content.pm.PackageManager; 27 import android.net.NetworkPolicyManager; 28 import android.util.ArraySet; 29 import android.util.Log; 30 31 import androidx.annotation.VisibleForTesting; 32 33 import java.io.PrintWriter; 34 import java.util.ArrayList; 35 import java.util.Arrays; 36 import java.util.List; 37 import java.util.Set; 38 39 /** A class to dynamically manage per apps {@link NetworkPolicyManager} POLICY_ flags. */ 40 public class DynamicDenylistManager { 41 42 private static final String TAG = "DynamicDenylistManager"; 43 private static final String PREF_KEY_MANUAL_DENY = "manual_denylist_preference"; 44 private static final String PREF_KEY_DYNAMIC_DENY = "dynamic_denylist_preference"; 45 46 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) 47 public static DynamicDenylistManager sInstance = null; 48 49 private final Context mContext; 50 private final NetworkPolicyManager mNetworkPolicyManager; 51 private final Object mLock = new Object(); 52 53 @VisibleForTesting 54 static final String PREF_KEY_MANUAL_DENYLIST_SYNCED = "manual_denylist_synced"; 55 56 /** @return a DynamicDenylistManager object */ getInstance(Context context)57 public static DynamicDenylistManager getInstance(Context context) { 58 synchronized (DynamicDenylistManager.class) { 59 if (sInstance == null) { 60 sInstance = new DynamicDenylistManager( 61 context, NetworkPolicyManager.from(context)); 62 } 63 return sInstance; 64 } 65 } 66 67 @VisibleForTesting DynamicDenylistManager(Context context, NetworkPolicyManager networkPolicyManager)68 DynamicDenylistManager(Context context, NetworkPolicyManager networkPolicyManager) { 69 mContext = context.getApplicationContext(); 70 mNetworkPolicyManager = networkPolicyManager; 71 syncPolicyIfNeeded(); 72 } 73 74 /** Sync the policy from {@link NetworkPolicyManager} if needed. */ syncPolicyIfNeeded()75 private void syncPolicyIfNeeded() { 76 if (getManualDenylistPref().contains(PREF_KEY_MANUAL_DENYLIST_SYNCED)) { 77 Log.i(TAG, "syncPolicyIfNeeded() ignore synced manual denylist"); 78 return; 79 } 80 81 final SharedPreferences.Editor editor = getManualDenylistPref().edit(); 82 final int[] existedUids = mNetworkPolicyManager 83 .getUidsWithPolicy(POLICY_REJECT_METERED_BACKGROUND); 84 if (existedUids != null && existedUids.length != 0) { 85 for (int uid : existedUids) { 86 editor.putInt(String.valueOf(uid), POLICY_REJECT_METERED_BACKGROUND); 87 } 88 } 89 editor.putInt(PREF_KEY_MANUAL_DENYLIST_SYNCED, POLICY_NONE).apply(); 90 } 91 92 /** Set policy flags for specific UID. */ setUidPolicyLocked(int uid, int policy)93 public void setUidPolicyLocked(int uid, int policy) { 94 Log.i(TAG, "setUidPolicyLocked: uid=" + uid + " policy=" + policy); 95 synchronized (mLock) { 96 mNetworkPolicyManager.setUidPolicy(uid, policy); 97 } 98 updateDenylistPref(uid, policy); 99 } 100 101 /** Suggest a list of package to set as POLICY_REJECT. */ setDenylist(Set<Integer> denylistTargetUids)102 public void setDenylist(Set<Integer> denylistTargetUids) { 103 if (denylistTargetUids == null) { 104 return; 105 } 106 final Set<Integer> manualDenylistUids = getDenylistAllUids(getManualDenylistPref()); 107 denylistTargetUids.removeAll(manualDenylistUids); 108 109 final Set<Integer> lastDynamicDenylistUids = getDenylistAllUids(getDynamicDenylistPref()); 110 if (lastDynamicDenylistUids.equals(denylistTargetUids)) { 111 Log.i(TAG, "setDenylist() ignore the same denylist with size: " 112 + lastDynamicDenylistUids.size()); 113 return; 114 } 115 116 final ArraySet<Integer> failedUids = new ArraySet<>(); 117 synchronized (mLock) { 118 // Set new added UIDs into REJECT policy. 119 for (Integer uidInteger : denylistTargetUids) { 120 if (uidInteger == null) { 121 continue; 122 } 123 final int uid = uidInteger.intValue(); 124 if (!lastDynamicDenylistUids.contains(uid)) { 125 try { 126 mNetworkPolicyManager.setUidPolicy(uid, POLICY_REJECT_METERED_BACKGROUND); 127 } catch (Exception e) { 128 Log.e(TAG, "failed to setUidPolicy(REJECT) for " + uid, e); 129 failedUids.add(uid); 130 } 131 } 132 } 133 // Unset removed UIDs back to NONE policy. 134 for (int uid : lastDynamicDenylistUids) { 135 if (!denylistTargetUids.contains(uid)) { 136 try { 137 mNetworkPolicyManager.setUidPolicy(uid, POLICY_NONE); 138 } catch (Exception e) { 139 Log.e(TAG, "failed to setUidPolicy(NONE) for " + uid, e); 140 } 141 } 142 } 143 } 144 145 // Store target denied uids into DynamicDenylistPref. 146 final SharedPreferences.Editor editor = getDynamicDenylistPref().edit(); 147 editor.clear(); 148 denylistTargetUids.forEach(uid -> { 149 if (!failedUids.contains(uid)) { 150 editor.putInt(String.valueOf(uid), POLICY_REJECT_METERED_BACKGROUND); 151 } 152 }); 153 editor.apply(); 154 } 155 156 /** Return true if the target uid is in {@link #getManualDenylistPref()}. */ isInManualDenylist(int uid)157 public boolean isInManualDenylist(int uid) { 158 return getManualDenylistPref().contains(String.valueOf(uid)); 159 } 160 161 /** Reset the UIDs in the denylist if needed. */ resetDenylistIfNeeded(String packageName, boolean force)162 public void resetDenylistIfNeeded(String packageName, boolean force) { 163 if (!force && !SETTINGS_PACKAGE_NAME.equals(packageName)) { 164 Log.w(TAG, "resetDenylistIfNeeded: invalid conditions"); 165 return; 166 } 167 synchronized (mLock) { 168 final int[] uids = mNetworkPolicyManager 169 .getUidsWithPolicy(POLICY_REJECT_METERED_BACKGROUND); 170 if (uids != null && uids.length != 0) { 171 Log.i(TAG, "resetDenylistIfNeeded: " + Arrays.toString(uids)); 172 for (int uid : uids) { 173 if (!getDenylistAllUids(getManualDenylistPref()).contains(uid)) { 174 mNetworkPolicyManager.setUidPolicy(uid, POLICY_NONE); 175 } 176 } 177 } else { 178 Log.w(TAG, "resetDenylistIfNeeded: there is no valid UIDs"); 179 } 180 } 181 clearSharedPreferences(); 182 } 183 184 /** Reset the POLICY_REJECT_METERED uids when device is boot completed. */ onBootComplete()185 public void onBootComplete() { 186 resetDenylistIfNeeded(/* packageName= */ null, /* force= */ true); 187 syncPolicyIfNeeded(); 188 } 189 190 /** Dump the data stored in the {@link SharedPreferences}. */ dump(PrintWriter writer)191 public void dump(PrintWriter writer) { 192 writer.println("Dump of DynamicDenylistManager:"); 193 final List<String> manualDenyList = 194 getPackageNames(mContext, getDenylistAllUids(getManualDenylistPref())); 195 writer.println("\tManualDenylist:"); 196 if (manualDenyList != null) { 197 manualDenyList.forEach(packageName -> writer.println("\t\t" + packageName)); 198 writer.flush(); 199 } 200 201 final List<String> dynamicDenyList = 202 getPackageNames(mContext, getDenylistAllUids(getDynamicDenylistPref())); 203 writer.println("\tDynamicDenylist:"); 204 if (dynamicDenyList != null) { 205 dynamicDenyList.forEach(packageName -> writer.println("\t\t" + packageName)); 206 writer.flush(); 207 } 208 } 209 getDenylistAllUids(SharedPreferences sharedPreferences)210 private Set<Integer> getDenylistAllUids(SharedPreferences sharedPreferences) { 211 final ArraySet<Integer> uids = new ArraySet<>(); 212 for (String key : sharedPreferences.getAll().keySet()) { 213 if (PREF_KEY_MANUAL_DENYLIST_SYNCED.equals(key)) { 214 continue; 215 } 216 try { 217 uids.add(Integer.parseInt(key)); 218 } catch (NumberFormatException e) { 219 Log.e(TAG, "getDenylistAllUids() unexpected format for " + key); 220 } 221 } 222 return uids; 223 } 224 updateDenylistPref(int uid, int policy)225 void updateDenylistPref(int uid, int policy) { 226 final String uidString = String.valueOf(uid); 227 if (policy != POLICY_REJECT_METERED_BACKGROUND) { 228 getManualDenylistPref().edit().remove(uidString).apply(); 229 } else { 230 getManualDenylistPref().edit().putInt(uidString, policy).apply(); 231 } 232 getDynamicDenylistPref().edit().remove(uidString).apply(); 233 } 234 clearSharedPreferences()235 void clearSharedPreferences() { 236 Log.i(TAG, "clearSharedPreferences()"); 237 getManualDenylistPref().edit().clear().apply(); 238 getDynamicDenylistPref().edit().clear().apply(); 239 } 240 241 @VisibleForTesting getManualDenylistPref()242 SharedPreferences getManualDenylistPref() { 243 return mContext.getSharedPreferences(PREF_KEY_MANUAL_DENY, Context.MODE_PRIVATE); 244 } 245 246 @VisibleForTesting getDynamicDenylistPref()247 SharedPreferences getDynamicDenylistPref() { 248 return mContext.getSharedPreferences(PREF_KEY_DYNAMIC_DENY, Context.MODE_PRIVATE); 249 } 250 getPackageNames(Context context, Set<Integer> uids)251 private static List<String> getPackageNames(Context context, Set<Integer> uids) { 252 if (uids == null || uids.isEmpty()) { 253 return null; 254 } 255 final PackageManager pm = context.getPackageManager(); 256 final List<String> packageNames = new ArrayList<>(uids.size()); 257 uids.forEach(uid -> packageNames.add(pm.getNameForUid(uid))); 258 return packageNames; 259 } 260 } 261