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 if (mNetworkPolicyManager == null) { 82 Log.w(TAG, "syncPolicyIfNeeded: invalid mNetworkPolicyManager"); 83 return; 84 } 85 86 final SharedPreferences.Editor editor = getManualDenylistPref().edit(); 87 final int[] existedUids = mNetworkPolicyManager 88 .getUidsWithPolicy(POLICY_REJECT_METERED_BACKGROUND); 89 if (existedUids != null && existedUids.length != 0) { 90 for (int uid : existedUids) { 91 editor.putInt(String.valueOf(uid), POLICY_REJECT_METERED_BACKGROUND); 92 } 93 } 94 editor.putInt(PREF_KEY_MANUAL_DENYLIST_SYNCED, POLICY_NONE).apply(); 95 } 96 97 /** Set policy flags for specific UID. */ setUidPolicyLocked(int uid, int policy)98 public void setUidPolicyLocked(int uid, int policy) { 99 if (mNetworkPolicyManager == null) { 100 Log.w(TAG, "setUidPolicyLocked: invalid mNetworkPolicyManager"); 101 return; 102 } 103 104 Log.i(TAG, "setUidPolicyLocked: uid=" + uid + " policy=" + policy); 105 synchronized (mLock) { 106 mNetworkPolicyManager.setUidPolicy(uid, policy); 107 } 108 updateDenylistPref(uid, policy); 109 } 110 111 /** Suggest a list of package to set as POLICY_REJECT. */ setDenylist(Set<Integer> denylistTargetUids)112 public void setDenylist(Set<Integer> denylistTargetUids) { 113 if (denylistTargetUids == null || mNetworkPolicyManager == null) { 114 return; 115 } 116 final Set<Integer> manualDenylistUids = getDenylistAllUids(getManualDenylistPref()); 117 denylistTargetUids.removeAll(manualDenylistUids); 118 119 final Set<Integer> lastDynamicDenylistUids = getDenylistAllUids(getDynamicDenylistPref()); 120 if (lastDynamicDenylistUids.equals(denylistTargetUids)) { 121 Log.i(TAG, "setDenylist() ignore the same denylist with size: " 122 + lastDynamicDenylistUids.size()); 123 return; 124 } 125 126 final ArraySet<Integer> failedUids = new ArraySet<>(); 127 synchronized (mLock) { 128 // Set new added UIDs into REJECT policy. 129 for (Integer uidInteger : denylistTargetUids) { 130 if (uidInteger == null) { 131 continue; 132 } 133 final int uid = uidInteger.intValue(); 134 if (!lastDynamicDenylistUids.contains(uid)) { 135 try { 136 mNetworkPolicyManager.setUidPolicy(uid, POLICY_REJECT_METERED_BACKGROUND); 137 } catch (Exception e) { 138 Log.e(TAG, "failed to setUidPolicy(REJECT) for " + uid, e); 139 failedUids.add(uid); 140 } 141 } 142 } 143 // Unset removed UIDs back to NONE policy. 144 for (int uid : lastDynamicDenylistUids) { 145 if (!denylistTargetUids.contains(uid)) { 146 try { 147 mNetworkPolicyManager.setUidPolicy(uid, POLICY_NONE); 148 } catch (Exception e) { 149 Log.e(TAG, "failed to setUidPolicy(NONE) for " + uid, e); 150 } 151 } 152 } 153 } 154 155 // Store target denied uids into DynamicDenylistPref. 156 final SharedPreferences.Editor editor = getDynamicDenylistPref().edit(); 157 editor.clear(); 158 denylistTargetUids.forEach(uid -> { 159 if (!failedUids.contains(uid)) { 160 editor.putInt(String.valueOf(uid), POLICY_REJECT_METERED_BACKGROUND); 161 } 162 }); 163 editor.apply(); 164 } 165 166 /** Return true if the target uid is in {@link #getManualDenylistPref()}. */ isInManualDenylist(int uid)167 public boolean isInManualDenylist(int uid) { 168 return getManualDenylistPref().contains(String.valueOf(uid)); 169 } 170 171 /** Reset the UIDs in the denylist if needed. */ resetDenylistIfNeeded(String packageName, boolean force)172 public void resetDenylistIfNeeded(String packageName, boolean force) { 173 if (!force && !SETTINGS_PACKAGE_NAME.equals(packageName)) { 174 Log.w(TAG, "resetDenylistIfNeeded: invalid conditions"); 175 return; 176 } 177 178 if (mNetworkPolicyManager == null) { 179 Log.w(TAG, "setUidPolicyLocked: invalid mNetworkPolicyManager"); 180 return; 181 } 182 183 synchronized (mLock) { 184 final int[] uids = mNetworkPolicyManager 185 .getUidsWithPolicy(POLICY_REJECT_METERED_BACKGROUND); 186 if (uids != null && uids.length != 0) { 187 Log.i(TAG, "resetDenylistIfNeeded: " + Arrays.toString(uids)); 188 for (int uid : uids) { 189 if (!getDenylistAllUids(getManualDenylistPref()).contains(uid)) { 190 mNetworkPolicyManager.setUidPolicy(uid, POLICY_NONE); 191 } 192 } 193 } else { 194 Log.w(TAG, "resetDenylistIfNeeded: there is no valid UIDs"); 195 } 196 } 197 clearSharedPreferences(); 198 } 199 200 /** Reset the POLICY_REJECT_METERED uids when device is boot completed. */ onBootComplete()201 public void onBootComplete() { 202 resetDenylistIfNeeded(/* packageName= */ null, /* force= */ true); 203 syncPolicyIfNeeded(); 204 } 205 206 /** Dump the data stored in the {@link SharedPreferences}. */ dump(PrintWriter writer)207 public void dump(PrintWriter writer) { 208 writer.println("Dump of DynamicDenylistManager:"); 209 final List<String> manualDenyList = 210 getPackageNames(mContext, getDenylistAllUids(getManualDenylistPref())); 211 writer.println("\tManualDenylist:"); 212 if (manualDenyList != null) { 213 manualDenyList.forEach(packageName -> writer.println("\t\t" + packageName)); 214 writer.flush(); 215 } 216 217 final List<String> dynamicDenyList = 218 getPackageNames(mContext, getDenylistAllUids(getDynamicDenylistPref())); 219 writer.println("\tDynamicDenylist:"); 220 if (dynamicDenyList != null) { 221 dynamicDenyList.forEach(packageName -> writer.println("\t\t" + packageName)); 222 writer.flush(); 223 } 224 } 225 getDenylistAllUids(SharedPreferences sharedPreferences)226 private Set<Integer> getDenylistAllUids(SharedPreferences sharedPreferences) { 227 final ArraySet<Integer> uids = new ArraySet<>(); 228 for (String key : sharedPreferences.getAll().keySet()) { 229 if (PREF_KEY_MANUAL_DENYLIST_SYNCED.equals(key)) { 230 continue; 231 } 232 try { 233 uids.add(Integer.parseInt(key)); 234 } catch (NumberFormatException e) { 235 Log.e(TAG, "getDenylistAllUids() unexpected format for " + key); 236 } 237 } 238 return uids; 239 } 240 updateDenylistPref(int uid, int policy)241 void updateDenylistPref(int uid, int policy) { 242 final String uidString = String.valueOf(uid); 243 if (policy != POLICY_REJECT_METERED_BACKGROUND) { 244 getManualDenylistPref().edit().remove(uidString).apply(); 245 } else { 246 getManualDenylistPref().edit().putInt(uidString, policy).apply(); 247 } 248 getDynamicDenylistPref().edit().remove(uidString).apply(); 249 } 250 clearSharedPreferences()251 void clearSharedPreferences() { 252 Log.i(TAG, "clearSharedPreferences()"); 253 getManualDenylistPref().edit().clear().apply(); 254 getDynamicDenylistPref().edit().clear().apply(); 255 } 256 257 @VisibleForTesting getManualDenylistPref()258 SharedPreferences getManualDenylistPref() { 259 return mContext.getSharedPreferences(PREF_KEY_MANUAL_DENY, Context.MODE_PRIVATE); 260 } 261 262 @VisibleForTesting getDynamicDenylistPref()263 SharedPreferences getDynamicDenylistPref() { 264 return mContext.getSharedPreferences(PREF_KEY_DYNAMIC_DENY, Context.MODE_PRIVATE); 265 } 266 getPackageNames(Context context, Set<Integer> uids)267 private static List<String> getPackageNames(Context context, Set<Integer> uids) { 268 if (uids == null || uids.isEmpty()) { 269 return null; 270 } 271 final PackageManager pm = context.getPackageManager(); 272 final List<String> packageNames = new ArrayList<>(uids.size()); 273 uids.forEach(uid -> packageNames.add(pm.getNameForUid(uid))); 274 return packageNames; 275 } 276 } 277