• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #19939

07 Aug 2025 03:38PM UTC coverage: 88.591% (+0.02%) from 88.572%
#19939

push

github

web-flow
Introduce a NameResolver for Android's `intent:` URIs (#12248)

Let grpc-binder clients find on-device services by [implicit Intent](https://developer.android.com/guide/components/intents-filters#Types) target URI, lifting the need to hard code a server's package name.

34677 of 39143 relevant lines covered (88.59%)

0.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.42
/../api/src/main/java/io/grpc/NameResolverRegistry.java
1
/*
2
 * Copyright 2019 The gRPC Authors
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 io.grpc;
18

19
import static com.google.common.base.Preconditions.checkArgument;
20

21
import com.google.common.annotations.VisibleForTesting;
22
import com.google.common.collect.ImmutableMap;
23
import com.google.errorprone.annotations.concurrent.GuardedBy;
24
import java.net.URI;
25
import java.util.ArrayList;
26
import java.util.Collections;
27
import java.util.HashMap;
28
import java.util.LinkedHashSet;
29
import java.util.List;
30
import java.util.Locale;
31
import java.util.Map;
32
import java.util.logging.Level;
33
import java.util.logging.Logger;
34
import javax.annotation.Nullable;
35
import javax.annotation.concurrent.ThreadSafe;
36

37
/**
38
 * Registry of {@link NameResolverProvider}s.  The {@link #getDefaultRegistry default instance}
39
 * loads providers at runtime through the Java service provider mechanism.
40
 *
41
 * @since 1.21.0
42
 */
43
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/4159")
44
@ThreadSafe
45
public final class NameResolverRegistry {
1✔
46
  private static final Logger logger = Logger.getLogger(NameResolverRegistry.class.getName());
1✔
47
  private static NameResolverRegistry instance;
48

49
  private final NameResolver.Factory factory = new NameResolverFactory();
1✔
50
  private static final String UNKNOWN_SCHEME = "unknown";
51
  @GuardedBy("this")
1✔
52
  private String defaultScheme = UNKNOWN_SCHEME;
53

54
  @GuardedBy("this")
1✔
55
  private final LinkedHashSet<NameResolverProvider> allProviders = new LinkedHashSet<>();
56
  /** Generated from {@code allProviders}. Is mapping from scheme key to the highest priority
57
   * {@link NameResolverProvider}. Is replaced instead of mutating. */
58
  @GuardedBy("this")
1✔
59
  private ImmutableMap<String, NameResolverProvider> effectiveProviders = ImmutableMap.of();
1✔
60

61
  public synchronized String getDefaultScheme() {
62
    return defaultScheme;
1✔
63
  }
64

65
  public NameResolverProvider getProviderForScheme(String scheme) {
66
    if (scheme == null) {
1✔
67
      return null;
1✔
68
    }
69
    return providers().get(scheme.toLowerCase(Locale.US));
1✔
70
  }
71

72
  /**
73
   * Register a provider.
74
   *
75
   * <p>If the provider's {@link NameResolverProvider#isAvailable isAvailable()} returns
76
   * {@code false}, this method will throw {@link IllegalArgumentException}.
77
   *
78
   * <p>Providers will be used in priority order. In case of ties, providers are used in
79
   * registration order.
80
   */
81
  public synchronized void register(NameResolverProvider provider) {
82
    addProvider(provider);
1✔
83
    refreshProviders();
1✔
84
  }
1✔
85

86
  private synchronized void addProvider(NameResolverProvider provider) {
87
    checkArgument(provider.isAvailable(), "isAvailable() returned false");
1✔
88
    allProviders.add(provider);
1✔
89
  }
1✔
90

91
  /**
92
   * Deregisters a provider.  No-op if the provider is not in the registry.
93
   *
94
   * @param provider the provider that was added to the register via {@link #register}.
95
   */
96
  public synchronized void deregister(NameResolverProvider provider) {
97
    allProviders.remove(provider);
1✔
98
    refreshProviders();
1✔
99
  }
1✔
100

101
  private synchronized void refreshProviders() {
102
    Map<String, NameResolverProvider> refreshedProviders = new HashMap<>();
1✔
103
    int maxPriority = Integer.MIN_VALUE;
1✔
104
    String refreshedDefaultScheme = UNKNOWN_SCHEME;
1✔
105
    // We prefer first-registered providers
106
    for (NameResolverProvider provider : allProviders) {
1✔
107
      String scheme = provider.getScheme();
1✔
108
      NameResolverProvider existing = refreshedProviders.get(scheme);
1✔
109
      if (existing == null || existing.priority() < provider.priority()) {
1✔
110
        refreshedProviders.put(scheme, provider);
1✔
111
      }
112
      if (maxPriority < provider.priority()) {
1✔
113
        maxPriority = provider.priority();
1✔
114
        refreshedDefaultScheme = provider.getScheme();
1✔
115
      }
116
    }
1✔
117
    effectiveProviders = ImmutableMap.copyOf(refreshedProviders);
1✔
118
    defaultScheme = refreshedDefaultScheme;
1✔
119
  }
1✔
120

121
  /**
122
   * Returns the default registry that loads providers via the Java service loader mechanism.
123
   */
124
  public static synchronized NameResolverRegistry getDefaultRegistry() {
125
    if (instance == null) {
1✔
126
      List<NameResolverProvider> providerList = ServiceProviders.loadAll(
1✔
127
          NameResolverProvider.class,
128
          getHardCodedClasses(),
1✔
129
          NameResolverProvider.class.getClassLoader(),
1✔
130
          new NameResolverPriorityAccessor());
131
      if (providerList.isEmpty()) {
1✔
132
        logger.warning("No NameResolverProviders found via ServiceLoader, including for DNS. This "
×
133
            + "is probably due to a broken build. If using ProGuard, check your configuration");
134
      }
135
      instance = new NameResolverRegistry();
1✔
136
      for (NameResolverProvider provider : providerList) {
1✔
137
        logger.fine("Service loader found " + provider);
1✔
138
        instance.addProvider(provider);
1✔
139
      }
1✔
140
      instance.refreshProviders();
1✔
141
    }
142
    return instance;
1✔
143
  }
144

145
  /**
146
   * Returns effective providers map from scheme to the highest priority NameResolverProvider of
147
   * that scheme.
148
   */
149
  @VisibleForTesting
150
  synchronized Map<String, NameResolverProvider> providers() {
151
    return effectiveProviders;
1✔
152
  }
153

154
  public NameResolver.Factory asFactory() {
155
    return factory;
1✔
156
  }
157

158
  @VisibleForTesting
159
  static List<Class<?>> getHardCodedClasses() {
160
    // Class.forName(String) is used to remove the need for ProGuard configuration. Note that
161
    // ProGuard does not detect usages of Class.forName(String, boolean, ClassLoader):
162
    // https://sourceforge.net/p/proguard/bugs/418/
163
    ArrayList<Class<?>> list = new ArrayList<>();
1✔
164
    try {
165
      list.add(Class.forName("io.grpc.internal.DnsNameResolverProvider"));
1✔
166
    } catch (ClassNotFoundException e) {
×
167
      logger.log(Level.FINE, "Unable to find DNS NameResolver", e);
×
168
    }
1✔
169
    try {
170
      list.add(Class.forName("io.grpc.binder.internal.IntentNameResolverProvider"));
×
171
    } catch (ClassNotFoundException e) {
1✔
172
      logger.log(Level.FINE, "Unable to find IntentNameResolverProvider", e);
1✔
173
    }
×
174
    return Collections.unmodifiableList(list);
1✔
175
  }
176

177
  private final class NameResolverFactory extends NameResolver.Factory {
1✔
178
    @Override
179
    @Nullable
180
    public NameResolver newNameResolver(URI targetUri, NameResolver.Args args) {
181
      NameResolverProvider provider = getProviderForScheme(targetUri.getScheme());
1✔
182
      return provider == null ? null : provider.newNameResolver(targetUri, args);
1✔
183
    }
184

185
    @Override
186
    public String getDefaultScheme() {
187
      return NameResolverRegistry.this.getDefaultScheme();
1✔
188
    }
189
  }
190

191
  private static final class NameResolverPriorityAccessor
192
      implements ServiceProviders.PriorityAccessor<NameResolverProvider> {
193
    @Override
194
    public boolean isAvailable(NameResolverProvider provider) {
195
      return provider.isAvailable();
1✔
196
    }
197

198
    @Override
199
    public int getPriority(NameResolverProvider provider) {
200
      return provider.priority();
1✔
201
    }
202
  }
203
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc