Home | History | Annotate | Download | only in net
      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_NETWORK_PRIVILEGED} 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_NETWORK_PRIVILEGED.equals(receiverInfo.permission)) {
    102                 // Receiver doesn't require the BROADCAST_NETWORK_PRIVILEGED 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