diff --git a/core/java/android/net/INetworkScoreService.aidl b/core/java/android/net/INetworkScoreService.aidl index f249daf876267..362ea9d6bc543 100644 --- a/core/java/android/net/INetworkScoreService.aidl +++ b/core/java/android/net/INetworkScoreService.aidl @@ -18,7 +18,7 @@ package android.net; import android.net.INetworkScoreCache; import android.net.NetworkKey; -import android.net.NetworkScorerAppManager; +import android.net.NetworkScorerAppData; import android.net.RecommendationRequest; import android.net.RecommendationResult; import android.net.ScoredNetwork; @@ -135,11 +135,11 @@ interface INetworkScoreService /** * Returns metadata about the active scorer or null if there is no active scorer. */ - NetworkScorerAppManager.NetworkScorerAppData getActiveScorer(); + NetworkScorerAppData getActiveScorer(); /** * Returns the list of available scorer apps. The list will be empty if there are * no valid scorers. */ - List getAllValidScorers(); + List getAllValidScorers(); } diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java index edfaee4ff5466..815d4807a39e4 100644 --- a/core/java/android/net/NetworkScoreManager.java +++ b/core/java/android/net/NetworkScoreManager.java @@ -18,7 +18,6 @@ package android.net; import static android.net.NetworkRecommendationProvider.EXTRA_RECOMMENDATION_RESULT; -import android.Manifest; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -26,7 +25,6 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; import android.content.Context; -import android.net.NetworkScorerAppManager.NetworkScorerAppData; import android.os.Bundle; import android.os.Handler; import android.os.RemoteCallback; diff --git a/core/java/android/net/NetworkScorerAppManager.aidl b/core/java/android/net/NetworkScorerAppData.aidl similarity index 91% rename from core/java/android/net/NetworkScorerAppManager.aidl rename to core/java/android/net/NetworkScorerAppData.aidl index d968343141b60..ee7f1d1efdf10 100644 --- a/core/java/android/net/NetworkScorerAppManager.aidl +++ b/core/java/android/net/NetworkScorerAppData.aidl @@ -16,4 +16,4 @@ package android.net; -parcelable NetworkScorerAppManager.NetworkScorerAppData; +parcelable NetworkScorerAppData; diff --git a/core/java/android/net/NetworkScorerAppData.java b/core/java/android/net/NetworkScorerAppData.java new file mode 100644 index 0000000000000..fca0a2e10bcdc --- /dev/null +++ b/core/java/android/net/NetworkScorerAppData.java @@ -0,0 +1,99 @@ +package android.net; + +import android.annotation.Nullable; +import android.content.ComponentName; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Holds metadata about a discovered network scorer/recommendation application. + * + * @hide + */ +public final class NetworkScorerAppData implements Parcelable { + /** UID of the scorer app. */ + public final int packageUid; + private final ComponentName mRecommendationService; + /** + * The {@link ComponentName} of the Activity to start before enabling the "connect to open + * wifi networks automatically" feature. + */ + private final ComponentName mEnableUseOpenWifiActivity; + + public NetworkScorerAppData(int packageUid, ComponentName recommendationServiceComp, + ComponentName enableUseOpenWifiActivity) { + this.packageUid = packageUid; + this.mRecommendationService = recommendationServiceComp; + this.mEnableUseOpenWifiActivity = enableUseOpenWifiActivity; + } + + protected NetworkScorerAppData(Parcel in) { + packageUid = in.readInt(); + mRecommendationService = ComponentName.readFromParcel(in); + mEnableUseOpenWifiActivity = ComponentName.readFromParcel(in); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(packageUid); + ComponentName.writeToParcel(mRecommendationService, dest); + ComponentName.writeToParcel(mEnableUseOpenWifiActivity, dest); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator CREATOR = + new Creator() { + @Override + public NetworkScorerAppData createFromParcel(Parcel in) { + return new NetworkScorerAppData(in); + } + + @Override + public NetworkScorerAppData[] newArray(int size) { + return new NetworkScorerAppData[size]; + } + }; + + public String getRecommendationServicePackageName() { + return mRecommendationService.getPackageName(); + } + + public ComponentName getRecommendationServiceComponent() { + return mRecommendationService; + } + + @Nullable + public ComponentName getEnableUseOpenWifiActivity() { + return mEnableUseOpenWifiActivity; + } + + @Override + public String toString() { + return "NetworkScorerAppData{" + + "packageUid=" + packageUid + + ", mRecommendationService=" + mRecommendationService + + ", mEnableUseOpenWifiActivity=" + mEnableUseOpenWifiActivity + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + NetworkScorerAppData that = (NetworkScorerAppData) o; + return packageUid == that.packageUid && + Objects.equals(mRecommendationService, that.mRecommendationService) && + Objects.equals(mEnableUseOpenWifiActivity, that.mEnableUseOpenWifiActivity); + } + + @Override + public int hashCode() { + return Objects.hash(packageUid, mRecommendationService, mEnableUseOpenWifiActivity); + } +} diff --git a/core/java/android/net/NetworkScorerAppManager.java b/core/java/android/net/NetworkScorerAppManager.java deleted file mode 100644 index bbc1c79eca765..0000000000000 --- a/core/java/android/net/NetworkScorerAppManager.java +++ /dev/null @@ -1,332 +0,0 @@ -/* - * 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.ComponentName; -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.Parcel; -import android.os.Parcelable; -import android.content.pm.ServiceInfo; -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; -import java.util.Objects; - -/** - * 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 final class NetworkScorerAppData implements Parcelable { - /** UID of the scorer app. */ - public final int packageUid; - private final ComponentName mRecommendationService; - /** - * The {@link ComponentName} of the Activity to start before enabling the "connect to open - * wifi networks automatically" feature. - */ - private final ComponentName mEnableUseOpenWifiActivity; - - public NetworkScorerAppData(int packageUid, ComponentName recommendationServiceComp, - ComponentName enableUseOpenWifiActivity) { - this.packageUid = packageUid; - this.mRecommendationService = recommendationServiceComp; - this.mEnableUseOpenWifiActivity = enableUseOpenWifiActivity; - } - - protected NetworkScorerAppData(Parcel in) { - packageUid = in.readInt(); - mRecommendationService = ComponentName.readFromParcel(in); - mEnableUseOpenWifiActivity = ComponentName.readFromParcel(in); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(packageUid); - ComponentName.writeToParcel(mRecommendationService, dest); - ComponentName.writeToParcel(mEnableUseOpenWifiActivity, dest); - } - - @Override - public int describeContents() { - return 0; - } - - public static final Creator CREATOR = - new Creator() { - @Override - public NetworkScorerAppData createFromParcel(Parcel in) { - return new NetworkScorerAppData(in); - } - - @Override - public NetworkScorerAppData[] newArray(int size) { - return new NetworkScorerAppData[size]; - } - }; - - public String getRecommendationServicePackageName() { - return mRecommendationService.getPackageName(); - } - - public ComponentName getRecommendationServiceComponent() { - return mRecommendationService; - } - - @Nullable public ComponentName getEnableUseOpenWifiActivity() { - return mEnableUseOpenWifiActivity; - } - - @Override - public String toString() { - return "NetworkScorerAppData{" + - "packageUid=" + packageUid + - ", mRecommendationService=" + mRecommendationService + - ", mEnableUseOpenWifiActivity=" + mEnableUseOpenWifiActivity + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - NetworkScorerAppData that = (NetworkScorerAppData) o; - return packageUid == that.packageUid && - Objects.equals(mRecommendationService, that.mRecommendationService) && - Objects.equals(mEnableUseOpenWifiActivity, that.mEnableUseOpenWifiActivity); - } - - @Override - public int hashCode() { - return Objects.hash(packageUid, mRecommendationService, mEnableUseOpenWifiActivity); - } - } - - /** - * Returns the list of available scorer apps. The list will be empty if there are - * no valid scorers. - */ - public List getAllValidScorers() { - return Collections.emptyList(); - } - - /** - * @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. - * - *

A network recommendation provider is any application which: - *

    - *
  • Is listed in the config_networkRecommendationPackageNames config. - *
  • Declares the {@link android.Manifest.permission#SCORE_NETWORKS} permission. - *
  • Includes a Service for {@link NetworkScoreManager#ACTION_RECOMMEND_NETWORKS}. - *
- */ - 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 potentialPkgs = getPotentialRecommendationProviderPackages(); - if (potentialPkgs.isEmpty()) { - if (DEBUG) { - Log.d(TAG, "No Network Recommendation Providers specified."); - } - return null; - } - - for (int i = 0; i < potentialPkgs.size(); i++) { - final String potentialPkg = potentialPkgs.get(i); - - // Look for the recommendation service class and required receiver. - final ServiceInfo serviceInfo = findRecommendationService(potentialPkg); - if (serviceInfo != null) { - final ComponentName serviceComponentName = - new ComponentName(potentialPkg, serviceInfo.name); - final ComponentName useOpenWifiNetworksActivity = - findUseOpenWifiNetworksActivity(serviceInfo); - return new NetworkScorerAppData(serviceInfo.applicationInfo.uid, - serviceComponentName, useOpenWifiNetworksActivity); - } else { - if (DEBUG) { - Log.d(TAG, potentialPkg + " does not have the required components, skipping."); - } - } - } - - // None of the configured packages are valid. - return null; - } - - @Nullable private ComponentName findUseOpenWifiNetworksActivity(ServiceInfo serviceInfo) { - if (serviceInfo.metaData == null) { - if (DEBUG) { - Log.d(TAG, "No metadata found on recommendation service."); - } - return null; - } - final String useOpenWifiPackage = serviceInfo.metaData - .getString(NetworkScoreManager.USE_OPEN_WIFI_PACKAGE_META_DATA); - if (TextUtils.isEmpty(useOpenWifiPackage)) { - if (DEBUG) { - Log.d(TAG, "No use_open_wifi_package metadata found."); - } - return null; - } - final Intent enableUseOpenWifiIntent = new Intent(NetworkScoreManager.ACTION_CUSTOM_ENABLE) - .setPackage(useOpenWifiPackage); - final ResolveInfo resolveActivityInfo = mContext.getPackageManager() - .resolveActivity(enableUseOpenWifiIntent, 0 /* flags */); - if (VERBOSE) { - Log.d(TAG, "Resolved " + enableUseOpenWifiIntent + " to " + serviceInfo); - } - - if (resolveActivityInfo != null && resolveActivityInfo.activityInfo != null) { - return resolveActivityInfo.activityInfo.getComponentName(); - } - - 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 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 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; - } - - @Nullable private ServiceInfo findRecommendationService(String packageName) { - final PackageManager pm = mContext.getPackageManager(); - final int resolveFlags = PackageManager.GET_META_DATA; - 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.serviceInfo; - } - - 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. - * - *

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; - } - - 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; - } -} diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java index b33538cbcb074..d54ebaafb5bb6 100644 --- a/services/core/java/com/android/server/NetworkScoreService.java +++ b/services/core/java/com/android/server/NetworkScoreService.java @@ -35,8 +35,7 @@ import android.net.INetworkScoreCache; import android.net.INetworkScoreService; import android.net.NetworkKey; import android.net.NetworkScoreManager; -import android.net.NetworkScorerAppManager; -import android.net.NetworkScorerAppManager.NetworkScorerAppData; +import android.net.NetworkScorerAppData; import android.net.RecommendationRequest; import android.net.RecommendationResult; import android.net.ScoredNetwork; @@ -132,10 +131,10 @@ public class NetworkScoreService extends INetworkScoreService.Stub { * manages the service connection. */ private class NetworkScorerPackageMonitor extends PackageMonitor { - final List mPackagesToWatch; + final String mPackageToWatch; - private NetworkScorerPackageMonitor(List packagesToWatch) { - mPackagesToWatch = packagesToWatch; + private NetworkScorerPackageMonitor(String packageToWatch) { + mPackageToWatch = packageToWatch; } @Override @@ -168,37 +167,27 @@ public class NetworkScoreService extends INetworkScoreService.Stub { evaluateBinding(packageName, true /* forceUnbind */); } - private void evaluateBinding(String scorerPackageName, boolean forceUnbind) { - if (!mPackagesToWatch.contains(scorerPackageName)) { + private void evaluateBinding(String changedPackageName, boolean forceUnbind) { + if (!mPackageToWatch.equals(changedPackageName)) { // Early exit when we don't care about the package that has changed. return; } if (DBG) { - Log.d(TAG, "Evaluating binding for: " + scorerPackageName + Log.d(TAG, "Evaluating binding for: " + changedPackageName + ", forceUnbind=" + forceUnbind); } + final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer(); if (activeScorer == null) { // Package change has invalidated a scorer, this will also unbind any service // connection. if (DBG) Log.d(TAG, "No active scorers available."); - unbindFromScoringServiceIfNeeded(); - } else if (activeScorer.getRecommendationServicePackageName().equals(scorerPackageName)) - { - // The active scoring service changed in some way. - if (DBG) { - Log.d(TAG, "Possible change to the active scorer: " - + activeScorer.getRecommendationServicePackageName()); - } + refreshBinding(); + } else { // The scoring service changed in some way. if (forceUnbind) { unbindFromScoringServiceIfNeeded(); } - bindToScoringServiceIfNeeded(activeScorer); - } else { - // One of the scoring apps on the device has changed and we may no longer be - // bound to the correct scoring app. The logic in bindToScoringServiceIfNeeded() - // will sort that out to leave us bound to the most recent active scorer. if (DBG) { Log.d(TAG, "Binding to " + activeScorer.getRecommendationServiceComponent() + " if needed."); @@ -272,60 +261,71 @@ public class NetworkScoreService extends INetworkScoreService.Stub { /** Called when the system is ready to run third-party code but before it actually does so. */ void systemReady() { if (DBG) Log.d(TAG, "systemReady"); - registerPackageMonitorIfNeeded(); registerRecommendationSettingsObserver(); - refreshRecommendationRequestTimeoutMs(); } /** Called when the system is ready for us to start third-party code. */ void systemRunning() { if (DBG) Log.d(TAG, "systemRunning"); - bindToScoringServiceIfNeeded(); } - private void onUserUnlocked(int userId) { + @VisibleForTesting + void onUserUnlocked(int userId) { + if (DBG) Log.d(TAG, "onUserUnlocked(" + userId + ")"); + refreshBinding(); + } + + private void refreshBinding() { + if (DBG) Log.d(TAG, "refreshBinding()"); + // Apply the default package name if the Setting isn't set. + mNetworkScorerAppManager.revertToDefaultIfNoActive(); registerPackageMonitorIfNeeded(); bindToScoringServiceIfNeeded(); } private void registerRecommendationSettingsObserver() { - final List providerPackages = - mNetworkScorerAppManager.getPotentialRecommendationProviderPackages(); - if (!providerPackages.isEmpty()) { - final Uri enabledUri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_ENABLED); - mContentObserver.observe(enabledUri, - ServiceHandler.MSG_RECOMMENDATIONS_ENABLED_CHANGED); - } + final Uri packageNameUri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_PACKAGE); + mContentObserver.observe(packageNameUri, + ServiceHandler.MSG_RECOMMENDATIONS_PACKAGE_CHANGED); final Uri timeoutUri = Global.getUriFor(Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS); mContentObserver.observe(timeoutUri, ServiceHandler.MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED); } + /** + * Ensures the package manager is registered to monitor the current active scorer. + * If a discrepancy is found any previous monitor will be cleaned up + * and a new monitor will be created. + * + * This method is idempotent. + */ private void registerPackageMonitorIfNeeded() { - if (DBG) Log.d(TAG, "registerPackageMonitorIfNeeded"); - final List providerPackages = - mNetworkScorerAppManager.getPotentialRecommendationProviderPackages(); + if (DBG) Log.d(TAG, "registerPackageMonitorIfNeeded()"); + final NetworkScorerAppData appData = mNetworkScorerAppManager.getActiveScorer(); synchronized (mPackageMonitorLock) { // Unregister the current monitor if needed. - if (mPackageMonitor != null) { + if (mPackageMonitor != null && (appData == null + || !appData.getRecommendationServicePackageName().equals( + mPackageMonitor.mPackageToWatch))) { if (DBG) { Log.d(TAG, "Unregistering package monitor for " - + mPackageMonitor.mPackagesToWatch); + + mPackageMonitor.mPackageToWatch); } mPackageMonitor.unregister(); mPackageMonitor = null; } - // Create and register the monitor if there are packages that could be providers. - if (!providerPackages.isEmpty()) { - mPackageMonitor = new NetworkScorerPackageMonitor(providerPackages); + // Create and register the monitor if a scorer is active. + if (appData != null && mPackageMonitor == null) { + mPackageMonitor = new NetworkScorerPackageMonitor( + appData.getRecommendationServicePackageName()); // TODO: Need to update when we support per-user scorers. http://b/23422763 mPackageMonitor.register(mContext, null /* thread */, UserHandle.SYSTEM, false /* externalStorage */); if (DBG) { Log.d(TAG, "Registered package monitor for " - + mPackageMonitor.mPackagesToWatch); + + mPackageMonitor.mPackageToWatch); } } } @@ -337,6 +337,13 @@ public class NetworkScoreService extends INetworkScoreService.Stub { bindToScoringServiceIfNeeded(scorerData); } + /** + * Ensures the service connection is bound to the current active scorer. + * If a discrepancy is found any previous connection will be cleaned up + * and a new connection will be created. + * + * This method is idempotent. + */ private void bindToScoringServiceIfNeeded(NetworkScorerAppData appData) { if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded(" + appData + ")"); if (appData != null) { @@ -365,6 +372,8 @@ public class NetworkScoreService extends INetworkScoreService.Stub { synchronized (mServiceConnectionLock) { if (mServiceConnection != null) { mServiceConnection.disconnect(mContext); + if (DBG) Log.d(TAG, "Disconnected from: " + + mServiceConnection.mAppData.getRecommendationServiceComponent()); } mServiceConnection = null; } @@ -653,17 +662,13 @@ public class NetworkScoreService extends INetworkScoreService.Stub { @Override public boolean setActiveScorer(String packageName) { - // TODO: For now, since SCORE_NETWORKS requires an app to be privileged, we allow such apps - // to directly set the scorer app rather than having to use the consent dialog. The - // assumption is that anyone bundling a scorer app with the system is trusted by the OEM to - // do the right thing and not enable this feature without explaining it to the user. - // In the future, should this API be opened to 3p apps, we will need to lock this down and - // figure out another way to streamline the UX. - - mContext.enforceCallingOrSelfPermission(permission.SCORE_NETWORKS, TAG); - - // Scorers (recommendation providers) are selected and no longer set. - return false; + // Only the system can set the active scorer + if (isCallerSystemProcess(getCallingUid()) || callerCanRequestScores()) { + return mNetworkScorerAppManager.setActiveScorer(packageName); + } else { + throw new SecurityException( + "Caller is neither the system process nor a score requester."); + } } /** @@ -700,7 +705,6 @@ public class NetworkScoreService extends INetworkScoreService.Stub { return null; } - /** * Returns metadata about the active scorer or null if there is no active scorer. */ @@ -727,7 +731,13 @@ public class NetworkScoreService extends INetworkScoreService.Stub { */ @Override public List getAllValidScorers() { - return mNetworkScorerAppManager.getAllValidScorers(); + // Only the system can access this data. + if (isCallerSystemProcess(getCallingUid()) || callerCanRequestScores()) { + return mNetworkScorerAppManager.getAllValidScorers(); + } else { + throw new SecurityException( + "Caller is neither the system process nor a score requester."); + } } @Override @@ -1159,7 +1169,7 @@ public class NetworkScoreService extends INetworkScoreService.Stub { @VisibleForTesting public final class ServiceHandler extends Handler { public static final int MSG_RECOMMENDATION_REQUEST_TIMEOUT = 1; - public static final int MSG_RECOMMENDATIONS_ENABLED_CHANGED = 2; + public static final int MSG_RECOMMENDATIONS_PACKAGE_CHANGED = 2; public static final int MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED = 3; public ServiceHandler(Looper looper) { @@ -1181,8 +1191,8 @@ public class NetworkScoreService extends INetworkScoreService.Stub { sendDefaultRecommendationResponse(request, remoteCallback); break; - case MSG_RECOMMENDATIONS_ENABLED_CHANGED: - bindToScoringServiceIfNeeded(); + case MSG_RECOMMENDATIONS_PACKAGE_CHANGED: + refreshBinding(); break; case MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED: diff --git a/services/core/java/com/android/server/NetworkScorerAppManager.java b/services/core/java/com/android/server/NetworkScorerAppManager.java new file mode 100644 index 0000000000000..2f4485a4ea4cb --- /dev/null +++ b/services/core/java/com/android/server/NetworkScorerAppManager.java @@ -0,0 +1,245 @@ +/* + * 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 com.android.server; + +import android.Manifest.permission; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.net.NetworkScoreManager; +import android.net.NetworkScorerAppData; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Internal class for discovering and managing the network scorer/recommendation application. + * + * @hide + */ +@VisibleForTesting +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; + private final SettingsFacade mSettingsFacade; + + public NetworkScorerAppManager(Context context) { + this(context, new SettingsFacade()); + } + + @VisibleForTesting + public NetworkScorerAppManager(Context context, SettingsFacade settingsFacade) { + mContext = context; + mSettingsFacade = settingsFacade; + } + + /** + * Returns the list of available scorer apps. The list will be empty if there are + * no valid scorers. + */ + @VisibleForTesting + public List getAllValidScorers() { + if (VERBOSE) Log.v(TAG, "getAllValidScorers()"); + final PackageManager pm = mContext.getPackageManager(); + final Intent serviceIntent = new Intent(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS); + final List resolveInfos = + pm.queryIntentServices(serviceIntent, PackageManager.GET_META_DATA); + if (resolveInfos == null || resolveInfos.isEmpty()) { + if (DEBUG) Log.d(TAG, "Found 0 Services able to handle " + serviceIntent); + return Collections.emptyList(); + } + + List appDataList = new ArrayList<>(); + for (int i = 0; i < resolveInfos.size(); i++) { + final ServiceInfo serviceInfo = resolveInfos.get(i).serviceInfo; + if (hasPermissions(serviceInfo.packageName)) { + if (VERBOSE) { + Log.v(TAG, serviceInfo.packageName + " is a valid scorer/recommender."); + } + final ComponentName serviceComponentName = + new ComponentName(serviceInfo.packageName, serviceInfo.name); + final ComponentName useOpenWifiNetworksActivity = + findUseOpenWifiNetworksActivity(serviceInfo); + appDataList.add( + new NetworkScorerAppData(serviceInfo.applicationInfo.uid, + serviceComponentName, useOpenWifiNetworksActivity)); + } else { + if (VERBOSE) Log.v(TAG, serviceInfo.packageName + + " is NOT a valid scorer/recommender."); + } + } + + return appDataList; + } + + @Nullable + private ComponentName findUseOpenWifiNetworksActivity(ServiceInfo serviceInfo) { + if (serviceInfo.metaData == null) { + if (DEBUG) { + Log.d(TAG, "No metadata found on " + serviceInfo.getComponentName()); + } + return null; + } + final String useOpenWifiPackage = serviceInfo.metaData + .getString(NetworkScoreManager.USE_OPEN_WIFI_PACKAGE_META_DATA); + if (TextUtils.isEmpty(useOpenWifiPackage)) { + if (DEBUG) { + Log.d(TAG, "No use_open_wifi_package metadata found on " + + serviceInfo.getComponentName()); + } + return null; + } + final Intent enableUseOpenWifiIntent = new Intent(NetworkScoreManager.ACTION_CUSTOM_ENABLE) + .setPackage(useOpenWifiPackage); + final ResolveInfo resolveActivityInfo = mContext.getPackageManager() + .resolveActivity(enableUseOpenWifiIntent, 0 /* flags */); + if (VERBOSE) { + Log.d(TAG, "Resolved " + enableUseOpenWifiIntent + " to " + resolveActivityInfo); + } + + if (resolveActivityInfo != null && resolveActivityInfo.activityInfo != null) { + return resolveActivityInfo.activityInfo.getComponentName(); + } + + 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 + @VisibleForTesting + public NetworkScorerAppData getActiveScorer() { + return getScorer(getNetworkRecommendationsPackage()); + } + + private NetworkScorerAppData getScorer(String packageName) { + if (TextUtils.isEmpty(packageName)) { + return null; + } + + // Otherwise return the recommendation provider (which may be null). + List apps = getAllValidScorers(); + for (int i = 0; i < apps.size(); i++) { + NetworkScorerAppData app = apps.get(i); + if (app.getRecommendationServicePackageName().equals(packageName)) { + return app; + } + } + + return null; + } + + private boolean hasPermissions(String packageName) { + final PackageManager pm = mContext.getPackageManager(); + return pm.checkPermission(permission.SCORE_NETWORKS, packageName) + == PackageManager.PERMISSION_GRANTED; + } + + /** + * Set the specified package as the default scorer application. + * + *

The caller must have permission to write to {@link Settings.Global}. + * + * @param packageName the packageName of the new scorer to use. If null, the scoring app will + * revert back to the configured default. 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. + */ + @VisibleForTesting + public boolean setActiveScorer(String packageName) { + String oldPackageName = getNetworkRecommendationsPackage(); + if (TextUtils.equals(oldPackageName, packageName)) { + // No change. + return true; + } + + Log.i(TAG, "Changing network scorer from " + oldPackageName + " to " + packageName); + + if (packageName == null) { + // revert to the default setting. + setNetworkRecommendationsPackage(getDefaultPackageSetting()); + return true; + } else { + // We only make the change if the new package is valid. + if (getScorer(packageName) != null) { + setNetworkRecommendationsPackage(packageName); + return true; + } else { + Log.w(TAG, "Requested network scorer is not valid: " + packageName); + return false; + } + } + } + + /** + * If the active scorer is null then revert to the default scorer. + */ + @VisibleForTesting + public void revertToDefaultIfNoActive() { + if (getActiveScorer() == null) { + final String defaultPackage = getDefaultPackageSetting(); + setNetworkRecommendationsPackage(defaultPackage); + Log.i(TAG, "Defaulted the network recommendations app to: " + defaultPackage); + } + } + + private String getDefaultPackageSetting() { + return mContext.getResources().getString( + R.string.config_defaultNetworkRecommendationProviderPackage); + } + + private String getNetworkRecommendationsPackage() { + return mSettingsFacade.getString(mContext, Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE); + } + + private void setNetworkRecommendationsPackage(String packageName) { + mSettingsFacade.putString(mContext, + Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE, packageName); + } + + /** + * Wrapper around Settings to make testing easier. + */ + public static class SettingsFacade { + public boolean putString(Context context, String name, String value) { + return Settings.Global.putString(context.getContentResolver(), name, value); + } + + public String getString(Context context, String name) { + return Settings.Global.getString(context.getContentResolver(), name); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java index 665f01f88b4ac..4141f2f722d6f 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java @@ -56,8 +56,7 @@ import android.net.INetworkRecommendationProvider; import android.net.INetworkScoreCache; import android.net.NetworkKey; import android.net.NetworkScoreManager; -import android.net.NetworkScorerAppManager; -import android.net.NetworkScorerAppManager.NetworkScorerAppData; +import android.net.NetworkScorerAppData; import android.net.RecommendationRequest; import android.net.RecommendationResult; import android.net.ScoredNetwork; @@ -201,10 +200,10 @@ public class NetworkScoreServiceTest { } @Test - public void testSystemRunning() { + public void testOnUserUnlocked() { when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(NEW_SCORER); - mNetworkScoreService.systemRunning(); + mNetworkScoreService.onUserUnlocked(0); verify(mContext).bindServiceAsUser(MockUtils.checkIntent( new Intent(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS) @@ -549,9 +548,9 @@ public class NetworkScoreServiceTest { } @Test - public void testSetActiveScorer_noScoreNetworksPermission() { - doThrow(new SecurityException()).when(mContext) - .enforceCallingOrSelfPermission(eq(permission.SCORE_NETWORKS), anyString()); + public void testSetActiveScorer_noRequestNetworkScoresPermission() { + when(mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES)) + .thenReturn(PackageManager.PERMISSION_DENIED); try { mNetworkScoreService.setActiveScorer(null); @@ -630,7 +629,7 @@ public class NetworkScoreServiceTest { @Test public void testIsCallerActiveScorer_noBoundService() throws Exception { - mNetworkScoreService.systemRunning(); + mNetworkScoreService.onUserUnlocked(0); assertFalse(mNetworkScoreService.isCallerActiveScorer(Binder.getCallingUid())); } @@ -651,7 +650,7 @@ public class NetworkScoreServiceTest { @Test public void testGetActiveScorerPackage_notActive() throws Exception { - mNetworkScoreService.systemRunning(); + mNetworkScoreService.onUserUnlocked(0); assertNull(mNetworkScoreService.getActiveScorerPackage()); } @@ -659,7 +658,7 @@ public class NetworkScoreServiceTest { @Test public void testGetActiveScorerPackage_active() throws Exception { when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(NEW_SCORER); - mNetworkScoreService.systemRunning(); + mNetworkScoreService.onUserUnlocked(0); assertEquals(NEW_SCORER.getRecommendationServicePackageName(), mNetworkScoreService.getActiveScorerPackage()); @@ -960,7 +959,7 @@ public class NetworkScoreServiceTest { return true; } }); - mNetworkScoreService.systemRunning(); + mNetworkScoreService.onUserUnlocked(0); } private void bindToScorer(boolean callerIsScorer) { @@ -974,7 +973,7 @@ public class NetworkScoreServiceTest { when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(appData); when(mContext.bindServiceAsUser(isA(Intent.class), isA(ServiceConnection.class), anyInt(), isA(UserHandle.class))).thenReturn(true); - mNetworkScoreService.systemRunning(); + mNetworkScoreService.onUserUnlocked(0); } private static class OnResultListener implements RemoteCallback.OnResultListener { diff --git a/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java b/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java similarity index 61% rename from core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java rename to services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java index 347024d6a29d8..e9a2d34b63aec 100644 --- a/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java @@ -14,13 +14,22 @@ * limitations under the License */ -package android.net; +package com.android.server; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; + +import static org.junit.Assert.assertFalse; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.Manifest.permission; import android.content.ComponentName; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -29,144 +38,84 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.Resources; -import android.net.NetworkScorerAppManager.NetworkScorerAppData; +import android.net.NetworkScoreManager; +import android.net.NetworkScorerAppData; import android.os.Bundle; import android.provider.Settings; -import android.test.InstrumentationTestCase; +import android.support.test.runner.AndroidJUnit4; import com.android.internal.R; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; import java.util.List; -public class NetworkScorerAppManagerTest extends InstrumentationTestCase { +@RunWith(AndroidJUnit4.class) +public class NetworkScorerAppManagerTest { @Mock private Context mMockContext; @Mock private PackageManager mMockPm; @Mock private Resources mResources; - @Mock private ContentResolver mContentResolver; - private Context mTargetContext; + @Mock private NetworkScorerAppManager.SettingsFacade mSettingsFacade; private NetworkScorerAppManager mNetworkScorerAppManager; + private List mAvailableServices; - @Override + @Before public void setUp() throws Exception { - super.setUp(); - - // Configuration needed to make mockito/dexcache work. - mTargetContext = getInstrumentation().getTargetContext(); - System.setProperty("dexmaker.dexcache", mTargetContext.getCacheDir().getPath()); - ClassLoader newClassLoader = getInstrumentation().getClass().getClassLoader(); - Thread.currentThread().setContextClassLoader(newClassLoader); - MockitoAnnotations.initMocks(this); + mAvailableServices = new ArrayList<>(); when(mMockContext.getPackageManager()).thenReturn(mMockPm); + when(mMockPm.queryIntentServices(Mockito.argThat(new ArgumentMatcher() { + @Override + public boolean matches(Object object) { + Intent intent = (Intent) object; + return NetworkScoreManager.ACTION_RECOMMEND_NETWORKS.equals(intent.getAction()); + } + }), eq(PackageManager.GET_META_DATA))).thenReturn(mAvailableServices); when(mMockContext.getResources()).thenReturn(mResources); - when(mMockContext.getContentResolver()).thenReturn(mTargetContext.getContentResolver()); - mNetworkScorerAppManager = new NetworkScorerAppManager(mMockContext); - } - - public void testGetPotentialRecommendationProviderPackages_emptyConfig() throws Exception { - setNetworkRecommendationPackageNames(/*no configured packages*/); - assertTrue(mNetworkScorerAppManager.getPotentialRecommendationProviderPackages().isEmpty()); - } - - public void testGetPotentialRecommendationProviderPackages_permissionNotGranted() - throws Exception { - setNetworkRecommendationPackageNames("package1"); - mockScoreNetworksDenied("package1"); - - assertTrue(mNetworkScorerAppManager.getPotentialRecommendationProviderPackages().isEmpty()); - } - - public void testGetPotentialRecommendationProviderPackages_permissionGranted() - throws Exception { - setNetworkRecommendationPackageNames("package1"); - mockScoreNetworksGranted("package1"); - - List potentialProviderPackages = - mNetworkScorerAppManager.getPotentialRecommendationProviderPackages(); - - assertFalse(potentialProviderPackages.isEmpty()); - assertEquals("package1", potentialProviderPackages.get(0)); - } - - public void testGetPotentialRecommendationProviderPackages_multipleConfigured() - throws Exception { - setNetworkRecommendationPackageNames("package1", "package2"); - mockScoreNetworksDenied("package1"); - mockScoreNetworksGranted("package2"); - - List potentialProviderPackages = - mNetworkScorerAppManager.getPotentialRecommendationProviderPackages(); - - assertEquals(1, potentialProviderPackages.size()); - assertEquals("package2", potentialProviderPackages.get(0)); - } - - public void testGetNetworkRecommendationProviderData_noPotentialPackages() throws Exception { - setNetworkRecommendationPackageNames(/*no configured packages*/); - assertNull(mNetworkScorerAppManager.getNetworkRecommendationProviderData()); - } - - public void testGetNetworkRecommendationProviderData_serviceMissing() throws Exception { - setNetworkRecommendationPackageNames("package1"); - mockScoreNetworksGranted("package1"); - - assertNull(mNetworkScorerAppManager.getNetworkRecommendationProviderData()); - } - - public void testGetNetworkRecommendationProviderData_scoreNetworksNotGranted() - throws Exception { - final ComponentName recoComponent = new ComponentName("package1", "class1"); - setNetworkRecommendationPackageNames(recoComponent.getPackageName()); - mockScoreNetworksDenied(recoComponent.getPackageName()); - mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */); - - assertNull(mNetworkScorerAppManager.getNetworkRecommendationProviderData()); - } - - public void testGetNetworkRecommendationProviderData_available() throws Exception { - final ComponentName recoComponent = new ComponentName("package1", "class1"); - setNetworkRecommendationPackageNames(recoComponent.getPackageName()); - mockScoreNetworksGranted(recoComponent.getPackageName()); - mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */); - - NetworkScorerAppData appData = - mNetworkScorerAppManager.getNetworkRecommendationProviderData(); - assertNotNull(appData); - assertEquals(recoComponent, appData.getRecommendationServiceComponent()); - assertEquals(924, appData.packageUid); + + mNetworkScorerAppManager = new NetworkScorerAppManager(mMockContext, mSettingsFacade); } + @Test public void testGetActiveScorer_providerAvailable() throws Exception { final ComponentName recoComponent = new ComponentName("package1", "class1"); - setNetworkRecommendationPackageNames(recoComponent.getPackageName()); + setNetworkRecoPackageSetting(recoComponent.getPackageName()); mockScoreNetworksGranted(recoComponent.getPackageName()); mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */); - ContentResolver cr = mTargetContext.getContentResolver(); - Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 1); - final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer(); assertNotNull(activeScorer); assertEquals(recoComponent, activeScorer.getRecommendationServiceComponent()); assertEquals(924, activeScorer.packageUid); } + @Test + public void testGetActiveScorer_permissionMissing() throws Exception { + final ComponentName recoComponent = new ComponentName("package1", "class1"); + setNetworkRecoPackageSetting(recoComponent.getPackageName()); + mockScoreNetworksDenied(recoComponent.getPackageName()); + mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */); + + final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer(); + assertNull(activeScorer); + } + + @Test public void testGetActiveScorer_providerAvailable_enableUseOpenWifiActivityNotSet() throws Exception { final ComponentName recoComponent = new ComponentName("package1", "class1"); - setNetworkRecommendationPackageNames(recoComponent.getPackageName()); + setNetworkRecoPackageSetting(recoComponent.getPackageName()); mockScoreNetworksGranted(recoComponent.getPackageName()); mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */, null /* enableUseOpenWifiPackageActivityPackage*/); - ContentResolver cr = mTargetContext.getContentResolver(); - Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 1); - final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer(); assertNotNull(activeScorer); assertEquals(recoComponent, activeScorer.getRecommendationServiceComponent()); @@ -174,17 +123,15 @@ public class NetworkScorerAppManagerTest extends InstrumentationTestCase { assertNull(activeScorer.getEnableUseOpenWifiActivity()); } + @Test public void testGetActiveScorer_providerAvailable_enableUseOpenWifiActivityNotResolved() throws Exception { final ComponentName recoComponent = new ComponentName("package1", "class1"); - setNetworkRecommendationPackageNames(recoComponent.getPackageName()); + setNetworkRecoPackageSetting(recoComponent.getPackageName()); mockScoreNetworksGranted(recoComponent.getPackageName()); mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */, "package2" /* enableUseOpenWifiPackageActivityPackage*/); - ContentResolver cr = mTargetContext.getContentResolver(); - Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 1); - final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer(); assertNotNull(activeScorer); assertEquals(recoComponent, activeScorer.getRecommendationServiceComponent()); @@ -192,19 +139,17 @@ public class NetworkScorerAppManagerTest extends InstrumentationTestCase { assertNull(activeScorer.getEnableUseOpenWifiActivity()); } + @Test public void testGetActiveScorer_providerAvailable_enableUseOpenWifiActivityResolved() throws Exception { final ComponentName recoComponent = new ComponentName("package1", "class1"); final ComponentName enableUseOpenWifiComponent = new ComponentName("package2", "class2"); - setNetworkRecommendationPackageNames(recoComponent.getPackageName()); + setNetworkRecoPackageSetting(recoComponent.getPackageName()); mockScoreNetworksGranted(recoComponent.getPackageName()); mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */, enableUseOpenWifiComponent.getPackageName()); mockEnableUseOpenWifiActivity(enableUseOpenWifiComponent); - ContentResolver cr = mTargetContext.getContentResolver(); - Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 1); - final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer(); assertNotNull(activeScorer); assertEquals(recoComponent, activeScorer.getRecommendationServiceComponent()); @@ -212,33 +157,105 @@ public class NetworkScorerAppManagerTest extends InstrumentationTestCase { assertEquals(enableUseOpenWifiComponent, activeScorer.getEnableUseOpenWifiActivity()); } - public void testGetActiveScorer_providerNotAvailable() + @Test + public void testGetActiveScorer_packageSettingIsNull() throws Exception { - ContentResolver cr = mTargetContext.getContentResolver(); - Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 1); + // NETWORK_RECOMMENDATIONS_PACKAGE is null final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer(); assertNull(activeScorer); } - public void testGetActiveScorer_recommendationsDisabled() throws Exception { + @Test + public void testGetActiveScorer_packageSettingIsInvalid() throws Exception { final ComponentName recoComponent = new ComponentName("package1", "class1"); - setNetworkRecommendationPackageNames(recoComponent.getPackageName()); + setDefaultNetworkRecommendationPackage(recoComponent.getPackageName()); mockScoreNetworksGranted(recoComponent.getPackageName()); - mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */); - ContentResolver cr = mTargetContext.getContentResolver(); - Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0); + // NETWORK_RECOMMENDATIONS_PACKAGE is set to a package that isn't a recommender. final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer(); assertNull(activeScorer); } - private void setNetworkRecommendationPackageNames(String... names) { - if (names == null) { - names = new String[0]; - } - when(mResources.getStringArray(R.array.config_networkRecommendationPackageNames)) - .thenReturn(names); + @Test + public void testSetActiveScorer_noChange() throws Exception { + String packageName = "package"; + setNetworkRecoPackageSetting(packageName); + + assertTrue(mNetworkScorerAppManager.setActiveScorer(packageName)); + verify(mSettingsFacade, never()).putString(any(), any(), any()); + } + + @Test + public void testSetActiveScorer_nullPackage() throws Exception { + String packageName = "package"; + String defaultPackage = "defaultPackage"; + setNetworkRecoPackageSetting(packageName); + setDefaultNetworkRecommendationPackage(defaultPackage); + + assertTrue(mNetworkScorerAppManager.setActiveScorer(null)); + verify(mSettingsFacade).putString(mMockContext, + Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE, defaultPackage); + } + + @Test + public void testSetActiveScorer_validPackage() throws Exception { + String packageName = "package"; + String newPackage = "newPackage"; + setNetworkRecoPackageSetting(packageName); + final ComponentName recoComponent = new ComponentName(newPackage, "class1"); + mockScoreNetworksGranted(recoComponent.getPackageName()); + mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */, null); + + assertTrue(mNetworkScorerAppManager.setActiveScorer(newPackage)); + verify(mSettingsFacade).putString(mMockContext, + Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE, newPackage); + } + + @Test + public void testSetActiveScorer_invalidPackage() throws Exception { + String packageName = "package"; + String newPackage = "newPackage"; + setNetworkRecoPackageSetting(packageName); + // newPackage doesn't resolve to a valid recommender + + assertFalse(mNetworkScorerAppManager.setActiveScorer(newPackage)); + verify(mSettingsFacade, never()).putString(any(), any(), any()); + } + + + @Test + public void testRevertToDefaultIfNoActive_notActive() throws Exception { + String defaultPackage = "defaultPackage"; + setDefaultNetworkRecommendationPackage(defaultPackage); + + mNetworkScorerAppManager.revertToDefaultIfNoActive(); + + verify(mSettingsFacade).putString(mMockContext, + Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE, defaultPackage); + } + + @Test + public void testRevertToDefaultIfNoActive_active() throws Exception { + String packageName = "package"; + setNetworkRecoPackageSetting(packageName); + final ComponentName recoComponent = new ComponentName(packageName, "class1"); + mockScoreNetworksGranted(recoComponent.getPackageName()); + mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */, null); + + mNetworkScorerAppManager.revertToDefaultIfNoActive(); + + verify(mSettingsFacade, never()).putString(any(), any(), any()); + } + + private void setNetworkRecoPackageSetting(String packageName) { + when(mSettingsFacade.getString(mMockContext, + Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE)).thenReturn(packageName); + } + + private void setDefaultNetworkRecommendationPackage(String name) { + when(mResources.getString(R.string.config_defaultNetworkRecommendationProviderPackage)) + .thenReturn(name); } private void mockScoreNetworksGranted(String packageName) { @@ -281,6 +298,8 @@ public class NetworkScorerAppManagerTest extends InstrumentationTestCase { && compName.getPackageName().equals(intent.getPackage()); } }), Mockito.eq(flags))).thenReturn(serviceInfo); + + mAvailableServices.add(serviceInfo); } private void mockEnableUseOpenWifiActivity(final ComponentName useOpenWifiComp) {