summaryrefslogtreecommitdiffstats
path: root/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/ServiceUtils.java
blob: 695c69666bd5c09497fe7ae938ab75dbda3267c1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.gecko.process;

import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import androidx.annotation.NonNull;

/* package */ final class ServiceUtils {
  private static final String DEFAULT_ISOLATED_CONTENT_SERVICE_NAME_SUFFIX = "0";

  private ServiceUtils() {}

  /**
   * @return StringBuilder containing the name of a service class but not qualifed with any unique
   *     identifiers.
   */
  private static StringBuilder startSvcName(@NonNull final GeckoProcessType type) {
    final StringBuilder builder = new StringBuilder(GeckoChildProcessServices.class.getName());
    builder.append("$").append(type);
    return builder;
  }

  /**
   * Given a service's GeckoProcessType, obtain the name of its class, including any qualifiers that
   * are needed to uniquely identify its manifest definition.
   */
  public static String buildSvcName(
      @NonNull final GeckoProcessType type, final String... suffixes) {
    final StringBuilder builder = startSvcName(type);

    for (final String suffix : suffixes) {
      builder.append(suffix);
    }

    return builder.toString();
  }

  /**
   * Given a service's GeckoProcessType, obtain the name of its class to be used for the purpose of
   * binding as an isolated service.
   *
   * <p>Content services are defined in the manifest as "tab0" through "tabN" for some value of N.
   * For the purposes of binding to an isolated content service, we simply need to repeatedly re-use
   * the definition of "tab0", the "0" being stored as the
   * DEFAULT_ISOLATED_CONTENT_SERVICE_NAME_SUFFIX constant.
   */
  public static String buildIsolatedSvcName(@NonNull final GeckoProcessType type) {
    if (type == GeckoProcessType.CONTENT) {
      return buildSvcName(type, DEFAULT_ISOLATED_CONTENT_SERVICE_NAME_SUFFIX);
    }

    // Non-content services do not require any unique IDs
    return buildSvcName(type);
  }

  /**
   * Given a service's GeckoProcessType, obtain the unqualified name of its class.
   *
   * @return The name of the class that hosts the implementation of the service corresponding to
   *     type, but without any unique identifiers that may be required to actually instantiate it.
   */
  private static String buildSvcNamePrefix(@NonNull final GeckoProcessType type) {
    return startSvcName(type).toString();
  }

  /**
   * Extracts flags from the manifest definition of a service.
   *
   * @param context Context to use for extraction
   * @param type Service type
   * @return flags that are specified in the service's definition in our manifest.
   * @see android.content.pm.ServiceInfo for explanation of the various flags.
   */
  public static int getServiceFlags(
      @NonNull final Context context, @NonNull final GeckoProcessType type) {
    final ComponentName component = new ComponentName(context, buildIsolatedSvcName(type));
    final PackageManager pkgMgr = context.getPackageManager();

    try {
      final ServiceInfo svcInfo = pkgMgr.getServiceInfo(component, 0);
      // svcInfo is never null
      return svcInfo.flags;
    } catch (final PackageManager.NameNotFoundException e) {
      throw new RuntimeException(e);
    }
  }

  /** Obtain the list of all services defined for |context|. */
  private static ServiceInfo[] getServiceList(@NonNull final Context context) {
    final PackageInfo packageInfo;
    try {
      packageInfo =
          context
              .getPackageManager()
              .getPackageInfo(context.getPackageName(), PackageManager.GET_SERVICES);
    } catch (final PackageManager.NameNotFoundException e) {
      throw new AssertionError("Should not happen: Can't get package info of own package");
    }
    return packageInfo.services;
  }

  /**
   * Count the number of service definitions in our manifest that satisfy bindings for a particular
   * service type.
   *
   * @param context Context object to use for extracting the service definitions
   * @param type The type of service to count
   * @return The number of available service definitions.
   */
  public static int getServiceCount(
      @NonNull final Context context, @NonNull final GeckoProcessType type) {
    final ServiceInfo[] svcList = getServiceList(context);
    final String serviceNamePrefix = buildSvcNamePrefix(type);

    int result = 0;
    for (final ServiceInfo svc : svcList) {
      final String svcName = svc.name;
      // If svcName starts with serviceNamePrefix, then both strings must either be equal
      // or else the first subsequent character in svcName must be a digit.
      // This guards against any future GeckoProcessType whose string representation shares
      // a common prefix with another GeckoProcessType value.
      if (svcName.startsWith(serviceNamePrefix)
          && (svcName.length() == serviceNamePrefix.length()
              || Character.isDigit(svcName.codePointAt(serviceNamePrefix.length())))) {
        ++result;
      }
    }

    if (result <= 0) {
      throw new RuntimeException("Could not count " + serviceNamePrefix + " services in manifest");
    }

    return result;
  }
}