• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2023 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.net.httpflags;
6 
7 import android.content.Context;
8 import android.content.Intent;
9 import android.content.pm.ApplicationInfo;
10 import android.content.pm.PackageManager;
11 import android.content.pm.ResolveInfo;
12 import android.os.Build;
13 
14 import androidx.annotation.Nullable;
15 import androidx.annotation.VisibleForTesting;
16 
17 import org.chromium.base.Log;
18 import org.chromium.net.impl.CronetManifest;
19 
20 import java.io.File;
21 import java.io.FileInputStream;
22 import java.io.FileNotFoundException;
23 import java.io.IOException;
24 
25 /**
26  * Utilities for loading HTTP flags.
27  *
28  * <p>HTTP flags are a generic mechanism by which the host system (i.e. the Android system image)
29  * can provide values for a variety of configuration knobs to alter the behavior of the HTTP client
30  * stack. The idea is that the host system can use some kind of centralized configuration mechanism
31  * to remotely push changes to these settings while collecting data on the results. This in turn
32  * enables A/B experiments, progressive configuration rollouts, etc.
33  *
34  * <p>Currently, the interface with the host system is defined as follows:
35  * <ol>
36  * <li>The Android system image must provide an Android app that exposes a service matching the
37  *     {@link #FLAGS_FILE_PROVIDER_INTENT_ACTION} action.
38  * <li>That Android app must expose a directory named after {@link #FLAGS_FILE_DIR_NAME} under the
39  *     app's {@link ApplicationInfo#deviceProtectedDataDir}.
40  * <li>That directory must contain a file named after {@link #FLAGS_FILE_NAME} that must be readable
41  *     by the process running {@link #load}.
42  * <li>The flag values are obtained from the contents of that file. The format is a binary proto
43  *     that can be read through {@link Flags#parseDelimitedFrom} - see `flags.proto` for details.
44  * </ol>
45  *
46  * @see HttpFlagsInterceptor
47  */
48 public final class HttpFlagsLoader {
HttpFlagsLoader()49     private HttpFlagsLoader() {}
50 
51     @VisibleForTesting
52     static final String FLAGS_FILE_PROVIDER_INTENT_ACTION = "android.net.http.FLAGS_FILE_PROVIDER";
53 
54     @VisibleForTesting static final String FLAGS_FILE_DIR_NAME = "app_httpflags";
55     @VisibleForTesting static final String FLAGS_FILE_NAME = "flags.binarypb";
56 
57     private static final String TAG = "HttpFlagsLoader";
58 
59     /**
60      * Locates and loads the HTTP flags file from the host system.
61      *
62      * Note that this is an expensive call.
63      *
64      * @return The contents of the flags file, or null if the flags file could not be loaded for any
65      * reason. In the latter case, the callee will take care of logging the failure.
66      *
67      * @see ResolvedFlags
68      */
69     @Nullable
load(Context context)70     public static Flags load(Context context) {
71         if (!CronetManifest.shouldReadHttpFlags(context)) {
72             Log.d(TAG, "Not loading HTTP flags because they are disabled in the manifest");
73             return null;
74         }
75 
76         try {
77             ApplicationInfo providerApplicationInfo = getProviderApplicationInfo(context);
78             if (providerApplicationInfo == null) return null;
79             Log.d(
80                     TAG,
81                     "Found application exporting HTTP flags: %s",
82                     providerApplicationInfo.packageName);
83 
84             File flagsFile = getFlagsFileFromProvider(context, providerApplicationInfo);
85             Log.d(TAG, "HTTP flags file path: %s", flagsFile.getAbsolutePath());
86 
87             Flags flags = loadFlagsFile(flagsFile);
88             if (flags == null) return null;
89             Log.d(TAG, "Successfully loaded HTTP flags: %s", flags);
90 
91             return flags;
92         } catch (RuntimeException exception) {
93             Log.e(TAG, "Unable to load HTTP flags file", exception);
94             return null;
95         }
96     }
97 
98     @Nullable
getProviderApplicationInfo(Context context)99     private static ApplicationInfo getProviderApplicationInfo(Context context) {
100         ResolveInfo resolveInfo =
101                 context.getPackageManager()
102                         .resolveService(
103                                 new Intent(FLAGS_FILE_PROVIDER_INTENT_ACTION),
104                                 // Make sure we only read flags files that are written by a package
105                                 // from the system image. This prevents random third-party apps
106                                 // from being able to inject flags into other apps, which would be
107                                 // a security risk.
108                                 PackageManager.MATCH_SYSTEM_ONLY);
109         if (resolveInfo == null) {
110             Log.w(
111                     TAG,
112                     "Unable to resolve the HTTP flags file provider package. This is expected if "
113                             + "the host system is not set up to provide HTTP flags.");
114             return null;
115         }
116 
117         return resolveInfo.serviceInfo.applicationInfo;
118     }
119 
getFlagsFileFromProvider( Context context, ApplicationInfo providerApplicationInfo)120     private static File getFlagsFileFromProvider(
121             Context context, ApplicationInfo providerApplicationInfo) {
122         return new File(
123                 new File(
124                         new File(
125                                 Build.VERSION.SDK_INT >= 24
126                                         ? providerApplicationInfo.deviceProtectedDataDir
127                                         : providerApplicationInfo.dataDir),
128                         FLAGS_FILE_DIR_NAME),
129                 FLAGS_FILE_NAME);
130     }
131 
132     @Nullable
loadFlagsFile(File file)133     private static Flags loadFlagsFile(File file) {
134         try (FileInputStream fileInputStream = new FileInputStream(file)) {
135             return Flags.parseDelimitedFrom(fileInputStream);
136         } catch (FileNotFoundException exception) {
137             Log.w(
138                     TAG,
139                     "HTTP flags file `%s` is missing. This is expected if HTTP flags functionality "
140                             + "is currently disabled in the host system.",
141                     file.getPath());
142             return null;
143         } catch (IOException exception) {
144             throw new RuntimeException("Unable to read HTTP flags file", exception);
145         }
146     }
147 }
148