diff --git a/core/java/android/net/NetworkScorerAppManager.java b/core/java/android/net/NetworkScorerAppManager.java index e555fa412a5b6..29291ca90467e 100644 --- a/core/java/android/net/NetworkScorerAppManager.java +++ b/core/java/android/net/NetworkScorerAppManager.java @@ -19,11 +19,9 @@ package android.net; import android.Manifest; import android.Manifest.permission; import android.annotation.Nullable; -import android.app.AppOpsManager; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.UserHandle; @@ -69,12 +67,32 @@ public final class NetworkScorerAppManager { */ public final String mConfigurationActivityClassName; + /** + * Optional class name of the scoring service we can bind to. Null if none is set. + */ + public final String mScoringServiceClassName; + public NetworkScorerAppData(String packageName, int packageUid, CharSequence scorerName, - @Nullable String configurationActivityClassName) { + @Nullable String configurationActivityClassName, + @Nullable String scoringServiceClassName) { mScorerName = scorerName; mPackageName = packageName; mPackageUid = packageUid; mConfigurationActivityClassName = configurationActivityClassName; + mScoringServiceClassName = scoringServiceClassName; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("NetworkScorerAppData{"); + sb.append("mPackageName='").append(mPackageName).append('\''); + sb.append(", mPackageUid=").append(mPackageUid); + sb.append(", mScorerName=").append(mScorerName); + sb.append(", mConfigurationActivityClassName='").append(mConfigurationActivityClassName) + .append('\''); + sb.append(", mScoringServiceClassName='").append(mScoringServiceClassName).append('\''); + sb.append('}'); + return sb.toString(); } } @@ -128,18 +146,27 @@ public final class NetworkScorerAppManager { Intent intent = new Intent(NetworkScoreManager.ACTION_CUSTOM_ENABLE); intent.setPackage(receiverInfo.packageName); List configActivities = pm.queryIntentActivities(intent, 0 /* flags */); - if (!configActivities.isEmpty()) { + if (configActivities != null && !configActivities.isEmpty()) { ActivityInfo activityInfo = configActivities.get(0).activityInfo; if (activityInfo != null) { configurationActivityClassName = activityInfo.name; } } + // Find the scoring service class we can bind to, if any. + String scoringServiceClassName = null; + Intent serviceIntent = new Intent(NetworkScoreManager.ACTION_SCORE_NETWORKS); + serviceIntent.setPackage(receiverInfo.packageName); + ResolveInfo resolveServiceInfo = pm.resolveService(serviceIntent, 0 /* flags */); + if (resolveServiceInfo != null && resolveServiceInfo.serviceInfo != null) { + scoringServiceClassName = resolveServiceInfo.serviceInfo.name; + } + // NOTE: loadLabel will attempt to load the receiver's label and fall back to the // app label if none is present. scorers.add(new NetworkScorerAppData(receiverInfo.packageName, receiverInfo.applicationInfo.uid, receiverInfo.loadLabel(pm), - configurationActivityClassName)); + configurationActivityClassName, scoringServiceClassName)); } return scorers; diff --git a/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java index 9ab62cc03bea7..e7aca78a07201 100644 --- a/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java +++ b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java @@ -23,10 +23,10 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; import android.net.NetworkScorerAppManager.NetworkScorerAppData; import android.os.UserHandle; import android.test.InstrumentationTestCase; -import android.util.Pair; import org.mockito.ArgumentMatcher; import org.mockito.Mock; @@ -58,25 +58,26 @@ public class NetworkScorerAppManagerTest extends InstrumentationTestCase { public void testGetAllValidScorers() throws Exception { // Package 1 - Valid scorer. - Pair package1 = buildResolveInfo("package1", 1, true, true, - false); + ResolveInfoHolder package1 = buildResolveInfo("package1", 1, true, true, false, false); // Package 2 - Receiver does not have BROADCAST_NETWORK_PRIVILEGED permission. - Pair package2 = buildResolveInfo("package2", 2, false, true, - false); + ResolveInfoHolder package2 = buildResolveInfo("package2", 2, false, true, false, false); // Package 3 - App does not have SCORE_NETWORKS permission. - Pair package3 = buildResolveInfo("package3", 3, true, false, - false); + ResolveInfoHolder package3 = buildResolveInfo("package3", 3, true, false, false, false); // Package 4 - Valid scorer w/ optional config activity. - Pair package4 = buildResolveInfo("package4", 4, true, true, true); + ResolveInfoHolder package4 = buildResolveInfo("package4", 4, true, true, true, false); - List> scorers = new ArrayList<>(); + // Package 5 - Valid scorer w/ optional service to bind to. + ResolveInfoHolder package5 = buildResolveInfo("package5", 5, true, true, false, true); + + List scorers = new ArrayList<>(); scorers.add(package1); scorers.add(package2); scorers.add(package3); scorers.add(package4); + scorers.add(package5); setScorers(scorers); Iterator result = @@ -94,14 +95,20 @@ public class NetworkScorerAppManagerTest extends InstrumentationTestCase { assertEquals(4, next.mPackageUid); assertEquals(".ConfigActivity", next.mConfigurationActivityClassName); + assertTrue(result.hasNext()); + next = result.next(); + assertEquals("package5", next.mPackageName); + assertEquals(5, next.mPackageUid); + assertEquals(".ScoringService", next.mScoringServiceClassName); + assertFalse(result.hasNext()); } - private void setScorers(List> scorers) { + private void setScorers(List scorers) { List receivers = new ArrayList<>(); - for (final Pair scorer : scorers) { - receivers.add(scorer.first); - if (scorer.second != null) { + for (final ResolveInfoHolder scorer : scorers) { + receivers.add(scorer.scorerResolveInfo); + if (scorer.configActivityResolveInfo != null) { // This scorer has a config activity. Mockito.when(mMockPm.queryIntentActivities( Mockito.argThat(new ArgumentMatcher() { @@ -110,10 +117,26 @@ public class NetworkScorerAppManagerTest extends InstrumentationTestCase { Intent intent = (Intent) object; return NetworkScoreManager.ACTION_CUSTOM_ENABLE.equals( intent.getAction()) - && scorer.first.activityInfo.packageName.equals( + && scorer.scorerResolveInfo.activityInfo.packageName.equals( intent.getPackage()); } - }), Mockito.eq(0))).thenReturn(Collections.singletonList(scorer.second)); + }), Mockito.eq(0))).thenReturn( + Collections.singletonList(scorer.configActivityResolveInfo)); + } + + if (scorer.serviceResolveInfo != null) { + // This scorer has a service to bind to + Mockito.when(mMockPm.resolveService( + Mockito.argThat(new ArgumentMatcher() { + @Override + public boolean matches(Object object) { + Intent intent = (Intent) object; + return NetworkScoreManager.ACTION_SCORE_NETWORKS.equals( + intent.getAction()) + && scorer.scorerResolveInfo.activityInfo.packageName.equals( + intent.getPackage()); + } + }), Mockito.eq(0))).thenReturn(scorer.serviceResolveInfo); } } @@ -128,9 +151,9 @@ public class NetworkScorerAppManagerTest extends InstrumentationTestCase { .thenReturn(receivers); } - private Pair buildResolveInfo(String packageName, int packageUid, - boolean hasReceiverPermission, boolean hasScorePermission, boolean hasConfigActivity) - throws Exception { + private ResolveInfoHolder buildResolveInfo(String packageName, int packageUid, + boolean hasReceiverPermission, boolean hasScorePermission, boolean hasConfigActivity, + boolean hasServiceInfo) throws Exception { Mockito.when(mMockPm.checkPermission(permission.SCORE_NETWORKS, packageName)) .thenReturn(hasScorePermission ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED); @@ -150,6 +173,27 @@ public class NetworkScorerAppManagerTest extends InstrumentationTestCase { configActivityInfo.activityInfo = new ActivityInfo(); configActivityInfo.activityInfo.name = ".ConfigActivity"; } - return Pair.create(resolveInfo, configActivityInfo); + + ResolveInfo serviceInfo = null; + if (hasServiceInfo) { + serviceInfo = new ResolveInfo(); + serviceInfo.serviceInfo = new ServiceInfo(); + serviceInfo.serviceInfo.name = ".ScoringService"; + } + + return new ResolveInfoHolder(resolveInfo, configActivityInfo, serviceInfo); + } + + private static class ResolveInfoHolder { + final ResolveInfo scorerResolveInfo; + final ResolveInfo configActivityResolveInfo; + final ResolveInfo serviceResolveInfo; + + public ResolveInfoHolder(ResolveInfo scorerResolveInfo, + ResolveInfo configActivityResolveInfo, ResolveInfo serviceResolveInfo) { + this.scorerResolveInfo = scorerResolveInfo; + this.configActivityResolveInfo = configActivityResolveInfo; + this.serviceResolveInfo = serviceResolveInfo; + } } } diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java index 879bb6f72895b..2a78f908649bd 100644 --- a/services/core/java/com/android/server/NetworkScoreService.java +++ b/services/core/java/com/android/server/NetworkScoreService.java @@ -18,10 +18,12 @@ package com.android.server; import android.Manifest.permission; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.net.INetworkScoreCache; import android.net.INetworkScoreService; @@ -30,6 +32,7 @@ import android.net.NetworkScorerAppManager; import android.net.NetworkScorerAppManager.NetworkScorerAppData; import android.net.ScoredNetwork; import android.os.Binder; +import android.os.IBinder; import android.os.PatternMatcher; import android.os.RemoteException; import android.os.UserHandle; @@ -55,17 +58,17 @@ import java.util.Set; */ public class NetworkScoreService extends INetworkScoreService.Stub { private static final String TAG = "NetworkScoreService"; + private static final boolean DBG = false; private final Context mContext; - private final Map mScoreCaches; - /** Lock used to update mReceiver when scorer package changes occur. */ - private Object mReceiverLock = new Object[0]; + private final Object mReceiverLock = new Object[0]; /** Clears scores when the active scorer package is no longer valid. */ @GuardedBy("mReceiverLock") private ScorerChangedReceiver mReceiver; + private ScoringServiceConnection mServiceConnection; private class ScorerChangedReceiver extends BroadcastReceiver { final String mRegisteredPackage; @@ -77,14 +80,23 @@ public class NetworkScoreService extends INetworkScoreService.Stub { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if ((Intent.ACTION_PACKAGE_CHANGED.equals(action) || - Intent.ACTION_PACKAGE_REPLACED.equals(action) || - Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) && - NetworkScorerAppManager.getActiveScorer(mContext) == null) { - // Package change has invalidated a scorer. - Log.i(TAG, "Package " + mRegisteredPackage + - " is no longer valid, disabling scoring"); - setScorerInternal(null); + if (Intent.ACTION_PACKAGE_CHANGED.equals(action) + || Intent.ACTION_PACKAGE_REPLACED.equals(action) + || Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) { + NetworkScorerAppData activeScorer = + NetworkScorerAppManager.getActiveScorer(mContext); + if (activeScorer == null) { + // Package change has invalidated a scorer. + Log.i(TAG, "Package " + mRegisteredPackage + + " is no longer valid, disabling scoring."); + setScorerInternal(null); + } else if (activeScorer.mScoringServiceClassName == null) { + // The scoring service is not available, make sure it's unbound. + unbindFromScoringServiceIfNeeded(); + } else { + // The scoring service may have changed or been added. + bindToScoringServiceIfNeeded(activeScorer); + } } } } @@ -96,6 +108,7 @@ 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"); ContentResolver cr = mContext.getContentResolver(); if (Settings.Global.getInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 0) == 0) { // On first run, we try to initialize the scorer to the one configured at build time. @@ -111,7 +124,14 @@ public class NetworkScoreService extends INetworkScoreService.Stub { registerPackageReceiverIfNeeded(); } + /** Called when the system is ready for us to start third-party code. */ + void systemRunning() { + if (DBG) Log.d(TAG, "systemRunning"); + bindToScoringServiceIfNeeded(); + } + private void registerPackageReceiverIfNeeded() { + if (DBG) Log.d(TAG, "registerPackageReceiverIfNeeded"); NetworkScorerAppData scorer = NetworkScorerAppManager.getActiveScorer(mContext); synchronized (mReceiverLock) { // Unregister the receiver if the current scorer has changed since last registration. @@ -142,6 +162,41 @@ public class NetworkScoreService extends INetworkScoreService.Stub { } } + private void bindToScoringServiceIfNeeded() { + if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded"); + NetworkScorerAppData scorerData = NetworkScorerAppManager.getActiveScorer(mContext); + bindToScoringServiceIfNeeded(scorerData); + } + + private void bindToScoringServiceIfNeeded(NetworkScorerAppData scorerData) { + if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded(" + scorerData + ")"); + if (scorerData != null && scorerData.mScoringServiceClassName != null) { + ComponentName componentName = + new ComponentName(scorerData.mPackageName, scorerData.mScoringServiceClassName); + // If we're connected to a different component then drop it. + if (mServiceConnection != null + && !mServiceConnection.mComponentName.equals(componentName)) { + unbindFromScoringServiceIfNeeded(); + } + + // If we're not connected at all then create a new connection. + if (mServiceConnection == null) { + mServiceConnection = new ScoringServiceConnection(componentName); + } + + // Make sure the connection is connected (idempotent) + mServiceConnection.connect(mContext); + } + } + + private void unbindFromScoringServiceIfNeeded() { + if (DBG) Log.d(TAG, "unbindFromScoringServiceIfNeeded"); + if (mServiceConnection != null) { + mServiceConnection.disconnect(mContext); + } + mServiceConnection = null; + } + @Override public boolean updateScores(ScoredNetwork[] networks) { if (!NetworkScorerAppManager.isCallerActiveScorer(mContext, getCallingUid())) { @@ -228,8 +283,10 @@ public class NetworkScoreService extends INetworkScoreService.Stub { /** Set the active scorer. Callers are responsible for checking permissions as appropriate. */ private boolean setScorerInternal(String packageName) { + if (DBG) Log.d(TAG, "setScorerInternal(" + packageName + ")"); long token = Binder.clearCallingIdentity(); try { + unbindFromScoringServiceIfNeeded(); // Preemptively clear scores even though the set operation could fail. We do this for // safety as scores should never be compared across apps; in practice, Settings should // only be allowing valid apps to be set as scorers, so failure here should be rare. @@ -237,8 +294,13 @@ public class NetworkScoreService extends INetworkScoreService.Stub { // Get the scorer that is about to be replaced, if any, so we can notify it directly. NetworkScorerAppData prevScorer = NetworkScorerAppManager.getActiveScorer(mContext); boolean result = NetworkScorerAppManager.setActiveScorer(mContext, packageName); + // Unconditionally attempt to bind to the current scorer. If setActiveScorer() failed + // then we'll attempt to restore the previous binding (if any), otherwise an attempt + // will be made to bind to the new scorer. + bindToScoringServiceIfNeeded(); if (result) { // new scorer successfully set registerPackageReceiverIfNeeded(); + Intent intent = new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED); if (prevScorer != null) { // Directly notify the old scorer. intent.setPackage(prevScorer.mPackageName); @@ -295,7 +357,6 @@ public class NetworkScoreService extends INetworkScoreService.Stub { return; } writer.println("Current scorer: " + currentScorer.mPackageName); - writer.flush(); for (INetworkScoreCache scoreCache : getScoreCaches()) { try { @@ -307,6 +368,12 @@ public class NetworkScoreService extends INetworkScoreService.Stub { } } } + if (mServiceConnection != null) { + mServiceConnection.dump(fd, writer, args); + } else { + writer.println("ScoringServiceConnection: null"); + } + writer.flush(); } /** @@ -320,4 +387,50 @@ public class NetworkScoreService extends INetworkScoreService.Stub { return new HashSet<>(mScoreCaches.values()); } } + + private static class ScoringServiceConnection implements ServiceConnection { + private final ComponentName mComponentName; + private boolean mBound = false; + + ScoringServiceConnection(ComponentName componentName) { + mComponentName = componentName; + } + + void connect(Context context) { + disconnect(context); + Intent service = new Intent(); + service.setComponent(mComponentName); + mBound = context.bindServiceAsUser(service, this, + Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, + UserHandle.SYSTEM); + if (!mBound) { + Log.w(TAG, "Bind call failed for " + service); + } + } + + void disconnect(Context context) { + try { + if (mBound) { + mBound = false; + context.unbindService(this); + } + } catch (RuntimeException e) { + Log.e(TAG, "Unbind failed.", e); + } + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + if (DBG) Log.d(TAG, "ScoringServiceConnection: " + name.flattenToString()); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + if (DBG) Log.d(TAG, "ScoringServiceConnection, disconnected: " + name.flattenToString()); + } + + public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + writer.println("ScoringServiceConnection: " + mComponentName + ", bound: " + mBound); + } + } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 81e43fd54fc2e..e7ae2b0fcfd52 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1424,6 +1424,12 @@ public final class SystemServer { } catch (Throwable e) { reportWtf("Notifying MmsService running", e); } + + try { + if (networkScoreF != null) networkScoreF.systemRunning(); + } catch (Throwable e) { + reportWtf("Notifying NetworkScoreService running", e); + } Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); } });