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