/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package android.net; import android.Manifest.permission; import android.annotation.Nullable; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; import android.util.Log; import com.android.internal.R; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Internal class for discovering and managing the network scorer/recommendation application. * * @hide */ public class NetworkScorerAppManager { private static final String TAG = "NetworkScorerAppManager"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); private final Context mContext; public NetworkScorerAppManager(Context context) { mContext = context; } /** * Holds metadata about a discovered network scorer/recommendation application. */ public static class NetworkScorerAppData { /** Package name of this scorer app. */ public final String packageName; /** UID of the scorer app. */ public final int packageUid; /** * Name of the recommendation service we can bind to. */ public final String recommendationServiceClassName; public NetworkScorerAppData(String packageName, int packageUid, String recommendationServiceClassName) { this.packageName = packageName; this.packageUid = packageUid; this.recommendationServiceClassName = recommendationServiceClassName; } @Override public String toString() { final StringBuilder sb = new StringBuilder("NetworkScorerAppData{"); sb.append("mPackageName='").append(packageName).append('\''); sb.append(", packageUid=").append(packageUid); sb.append(", recommendationServiceClassName='") .append(recommendationServiceClassName).append('\''); sb.append('}'); return sb.toString(); } } /** * @return A {@link NetworkScorerAppData} instance containing information about the * best configured network recommendation provider installed or {@code null} * if none of the configured packages can recommend networks. * * <p>A network recommendation provider is any application which: * <ul> * <li>Is listed in the <code>config_networkRecommendationPackageNames</code> config. * <li>Declares the {@link android.Manifest.permission#SCORE_NETWORKS} permission. * <li>Includes a Service for {@link NetworkScoreManager#ACTION_RECOMMEND_NETWORKS}. * </ul> */ public NetworkScorerAppData getNetworkRecommendationProviderData() { // Network recommendation apps can only run as the primary user right now. // http://b/23422763 if (UserHandle.getCallingUserId() != UserHandle.USER_SYSTEM) { return null; } final List<String> potentialPkgs = getPotentialRecommendationProviderPackages(); if (potentialPkgs.isEmpty()) { if (DEBUG) { Log.d(TAG, "No Network Recommendation Providers specified."); } return null; } final PackageManager pm = mContext.getPackageManager(); for (int i = 0; i < potentialPkgs.size(); i++) { final String potentialPkg = potentialPkgs.get(i); // Look for the recommendation service class and required receiver. final ResolveInfo resolveServiceInfo = findRecommendationService(potentialPkg); if (resolveServiceInfo != null) { return new NetworkScorerAppData(potentialPkg, resolveServiceInfo.serviceInfo.applicationInfo.uid, resolveServiceInfo.serviceInfo.name); } else { if (DEBUG) { Log.d(TAG, potentialPkg + " does not have the required components, skipping."); } } } // None of the configured packages are valid. return null; } /** * @return A priority order list of package names that have been granted the * permission needed for them to act as a network recommendation provider. * The packages in the returned list may not contain the other required * network recommendation provider components so additional checks are required * before making a package the network recommendation provider. */ public List<String> getPotentialRecommendationProviderPackages() { final String[] packageArray = mContext.getResources().getStringArray( R.array.config_networkRecommendationPackageNames); if (packageArray == null || packageArray.length == 0) { if (DEBUG) { Log.d(TAG, "No Network Recommendation Providers specified."); } return Collections.emptyList(); } if (VERBOSE) { Log.d(TAG, "Configured packages: " + TextUtils.join(", ", packageArray)); } List<String> packages = new ArrayList<>(); final PackageManager pm = mContext.getPackageManager(); for (String potentialPkg : packageArray) { if (pm.checkPermission(permission.SCORE_NETWORKS, potentialPkg) == PackageManager.PERMISSION_GRANTED) { packages.add(potentialPkg); } else { if (DEBUG) { Log.d(TAG, potentialPkg + " has not been granted " + permission.SCORE_NETWORKS + ", skipping."); } } } return packages; } private ResolveInfo findRecommendationService(String packageName) { final PackageManager pm = mContext.getPackageManager(); final int resolveFlags = 0; final Intent serviceIntent = new Intent(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS); serviceIntent.setPackage(packageName); final ResolveInfo resolveServiceInfo = pm.resolveService(serviceIntent, resolveFlags); if (VERBOSE) { Log.d(TAG, "Resolved " + serviceIntent + " to " + resolveServiceInfo); } if (resolveServiceInfo != null && resolveServiceInfo.serviceInfo != null) { return resolveServiceInfo; } if (VERBOSE) { Log.v(TAG, packageName + " does not have a service for " + serviceIntent); } return null; } /** * Get the application to use for scoring networks. * * @return the scorer app info or null if scoring is disabled (including if no scorer was ever * selected) or if the previously-set scorer is no longer a valid scorer app (e.g. because * it was disabled or uninstalled). */ @Nullable public NetworkScorerAppData getActiveScorer() { if (isNetworkRecommendationsDisabled()) { // If recommendations are disabled then there can't be an active scorer. return null; } // Otherwise return the recommendation provider (which may be null). return getNetworkRecommendationProviderData(); } /** * Set the specified package as the default scorer application. * * <p>The caller must have permission to write to {@link android.provider.Settings.Global}. * * @param packageName the packageName of the new scorer to use. If null, scoring will be * disabled. Otherwise, the scorer will only be set if it is a valid scorer application. * @return true if the scorer was changed, or false if the package is not a valid scorer or * a valid network recommendation provider exists. * @deprecated Scorers are now selected from a configured list. */ @Deprecated public boolean setActiveScorer(String packageName) { return false; } /** Determine whether the application with the given UID is the enabled scorer. */ @Deprecated // Use NetworkScoreManager.isCallerActiveScorer() public boolean isCallerActiveScorer(int callingUid) { NetworkScorerAppData defaultApp = getActiveScorer(); if (defaultApp == null) { return false; } return callingUid == defaultApp.packageUid; } private boolean isNetworkRecommendationsDisabled() { final ContentResolver cr = mContext.getContentResolver(); // A value of 1 indicates enabled. return Settings.Global.getInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0) != 1; } }