1 /* 2 * Copyright (C) 2014 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 android.net; 18 19 import android.Manifest; 20 import android.Manifest.permission; 21 import android.annotation.Nullable; 22 import android.app.AppOpsManager; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.pm.ActivityInfo; 26 import android.content.pm.ApplicationInfo; 27 import android.content.pm.PackageManager; 28 import android.content.pm.ResolveInfo; 29 import android.os.UserHandle; 30 import android.provider.Settings; 31 import android.text.TextUtils; 32 import android.util.Log; 33 34 import java.util.ArrayList; 35 import java.util.Collection; 36 import java.util.List; 37 38 /** 39 * Internal class for managing the primary network scorer application. 40 * 41 * TODO: Rename this to something more generic. 42 * 43 * @hide 44 */ 45 public final class NetworkScorerAppManager { 46 private static final String TAG = "NetworkScorerAppManager"; 47 48 private static final Intent SCORE_INTENT = 49 new Intent(NetworkScoreManager.ACTION_SCORE_NETWORKS); 50 51 /** This class cannot be instantiated. */ NetworkScorerAppManager()52 private NetworkScorerAppManager() {} 53 54 public static class NetworkScorerAppData { 55 /** Package name of this scorer app. */ 56 public final String mPackageName; 57 58 /** UID of the scorer app. */ 59 public final int mPackageUid; 60 61 /** Name of this scorer app for display. */ 62 public final CharSequence mScorerName; 63 64 /** 65 * Optional class name of a configuration activity. Null if none is set. 66 * 67 * @see NetworkScoreManager#ACTION_CUSTOM_ENABLE 68 */ 69 public final String mConfigurationActivityClassName; 70 NetworkScorerAppData(String packageName, int packageUid, CharSequence scorerName, @Nullable String configurationActivityClassName)71 public NetworkScorerAppData(String packageName, int packageUid, CharSequence scorerName, 72 @Nullable String configurationActivityClassName) { 73 mScorerName = scorerName; 74 mPackageName = packageName; 75 mPackageUid = packageUid; 76 mConfigurationActivityClassName = configurationActivityClassName; 77 } 78 } 79 80 /** 81 * Returns the list of available scorer apps. 82 * 83 * <p>A network scorer is any application which: 84 * <ul> 85 * <li>Declares the {@link android.Manifest.permission#SCORE_NETWORKS} permission. 86 * <li>Includes a receiver for {@link NetworkScoreManager#ACTION_SCORE_NETWORKS} guarded by the 87 * {@link android.Manifest.permission#BROADCAST_NETWORK_PRIVILEGED} permission. 88 * </ul> 89 * 90 * @return the list of scorers, or the empty list if there are no valid scorers. 91 */ getAllValidScorers(Context context)92 public static Collection<NetworkScorerAppData> getAllValidScorers(Context context) { 93 List<NetworkScorerAppData> scorers = new ArrayList<>(); 94 95 PackageManager pm = context.getPackageManager(); 96 // Only apps installed under the primary user of the device can be scorers. 97 List<ResolveInfo> receivers = 98 pm.queryBroadcastReceivers(SCORE_INTENT, 0 /* flags */, UserHandle.USER_OWNER); 99 for (ResolveInfo receiver : receivers) { 100 // This field is a misnomer, see android.content.pm.ResolveInfo#activityInfo 101 final ActivityInfo receiverInfo = receiver.activityInfo; 102 if (receiverInfo == null) { 103 // Should never happen with queryBroadcastReceivers, but invalid nonetheless. 104 continue; 105 } 106 if (!permission.BROADCAST_NETWORK_PRIVILEGED.equals(receiverInfo.permission)) { 107 // Receiver doesn't require the BROADCAST_NETWORK_PRIVILEGED permission, which means 108 // anyone could trigger network scoring and flood the framework with score requests. 109 continue; 110 } 111 if (pm.checkPermission(permission.SCORE_NETWORKS, receiverInfo.packageName) != 112 PackageManager.PERMISSION_GRANTED) { 113 // Application doesn't hold the SCORE_NETWORKS permission, so the user never 114 // approved it as a network scorer. 115 continue; 116 } 117 118 // Optionally, this package may specify a configuration activity. 119 String configurationActivityClassName = null; 120 Intent intent = new Intent(NetworkScoreManager.ACTION_CUSTOM_ENABLE); 121 intent.setPackage(receiverInfo.packageName); 122 List<ResolveInfo> configActivities = pm.queryIntentActivities(intent, 0 /* flags */); 123 if (!configActivities.isEmpty()) { 124 ActivityInfo activityInfo = configActivities.get(0).activityInfo; 125 if (activityInfo != null) { 126 configurationActivityClassName = activityInfo.name; 127 } 128 } 129 130 // NOTE: loadLabel will attempt to load the receiver's label and fall back to the app 131 // label if none is present. 132 scorers.add(new NetworkScorerAppData(receiverInfo.packageName, 133 receiverInfo.applicationInfo.uid, receiverInfo.loadLabel(pm), 134 configurationActivityClassName)); 135 } 136 137 return scorers; 138 } 139 140 /** 141 * Get the application to use for scoring networks. 142 * 143 * @return the scorer app info or null if scoring is disabled (including if no scorer was ever 144 * selected) or if the previously-set scorer is no longer a valid scorer app (e.g. because 145 * it was disabled or uninstalled). 146 */ getActiveScorer(Context context)147 public static NetworkScorerAppData getActiveScorer(Context context) { 148 String scorerPackage = Settings.Global.getString(context.getContentResolver(), 149 Settings.Global.NETWORK_SCORER_APP); 150 return getScorer(context, scorerPackage); 151 } 152 153 /** 154 * Set the specified package as the default scorer application. 155 * 156 * <p>The caller must have permission to write to {@link android.provider.Settings.Global}. 157 * 158 * @param context the context of the calling application 159 * @param packageName the packageName of the new scorer to use. If null, scoring will be 160 * disabled. Otherwise, the scorer will only be set if it is a valid scorer application. 161 * @return true if the scorer was changed, or false if the package is not a valid scorer. 162 */ setActiveScorer(Context context, String packageName)163 public static boolean setActiveScorer(Context context, String packageName) { 164 String oldPackageName = Settings.Global.getString(context.getContentResolver(), 165 Settings.Global.NETWORK_SCORER_APP); 166 if (TextUtils.equals(oldPackageName, packageName)) { 167 // No change. 168 return true; 169 } 170 171 Log.i(TAG, "Changing network scorer from " + oldPackageName + " to " + packageName); 172 173 if (packageName == null) { 174 Settings.Global.putString(context.getContentResolver(), 175 Settings.Global.NETWORK_SCORER_APP, null); 176 return true; 177 } else { 178 // We only make the change if the new package is valid. 179 if (getScorer(context, packageName) != null) { 180 Settings.Global.putString(context.getContentResolver(), 181 Settings.Global.NETWORK_SCORER_APP, packageName); 182 return true; 183 } else { 184 Log.w(TAG, "Requested network scorer is not valid: " + packageName); 185 return false; 186 } 187 } 188 } 189 190 /** Determine whether the application with the given UID is the enabled scorer. */ isCallerActiveScorer(Context context, int callingUid)191 public static boolean isCallerActiveScorer(Context context, int callingUid) { 192 NetworkScorerAppData defaultApp = getActiveScorer(context); 193 if (defaultApp == null) { 194 return false; 195 } 196 if (callingUid != defaultApp.mPackageUid) { 197 return false; 198 } 199 // To be extra safe, ensure the caller holds the SCORE_NETWORKS permission. It always 200 // should, since it couldn't become the active scorer otherwise, but this can't hurt. 201 return context.checkCallingPermission(Manifest.permission.SCORE_NETWORKS) == 202 PackageManager.PERMISSION_GRANTED; 203 } 204 205 /** Returns the {@link NetworkScorerAppData} for the given app, or null if it's not a scorer. */ getScorer(Context context, String packageName)206 public static NetworkScorerAppData getScorer(Context context, String packageName) { 207 if (TextUtils.isEmpty(packageName)) { 208 return null; 209 } 210 Collection<NetworkScorerAppData> applications = getAllValidScorers(context); 211 for (NetworkScorerAppData app : applications) { 212 if (packageName.equals(app.mPackageName)) { 213 return app; 214 } 215 } 216 return null; 217 } 218 } 219