1 /*
2  * Copyright (C) 2018 The Android Open Source Project
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 com.android.server.am;
18 
19 import android.annotation.NonNull;
20 import android.content.ContentResolver;
21 import android.database.ContentObserver;
22 import android.net.Uri;
23 import android.net.LocalSocketAddress;
24 import android.net.LocalSocket;
25 import android.os.AsyncTask;
26 import android.os.Build;
27 import android.os.SystemProperties;
28 import android.provider.DeviceConfig;
29 import android.provider.Settings;
30 import android.text.TextUtils;
31 import android.util.Slog;
32 import android.util.proto.ProtoInputStream;
33 import android.util.proto.ProtoOutputStream;
34 
35 import com.android.internal.annotations.VisibleForTesting;
36 
37 import android.aconfigd.Aconfigd.StorageRequestMessage;
38 import android.aconfigd.Aconfigd.StorageRequestMessages;
39 import android.aconfigd.Aconfigd.StorageReturnMessage;
40 import android.aconfigd.Aconfigd.StorageReturnMessages;
41 import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon;
42 
43 import java.io.DataInputStream;
44 import java.io.DataOutputStream;
45 import java.io.BufferedReader;
46 import java.io.File;
47 import java.io.FileReader;
48 import java.io.IOException;
49 import java.util.HashSet;
50 import java.util.HashMap;
51 import java.util.Map;
52 import java.util.List;
53 import java.util.ArrayList;
54 
55 /**
56  * Maps system settings to system properties.
57  * <p>The properties are dynamically updated when settings change.
58  * @hide
59  */
60 public class SettingsToPropertiesMapper {
61 
62     private static final String TAG = "SettingsToPropertiesMapper";
63 
64     private static final String SYSTEM_PROPERTY_PREFIX = "persist.device_config.";
65 
66     private static final String RESET_PERFORMED_PROPERTY = "device_config.reset_performed";
67 
68     private static final String RESET_RECORD_FILE_PATH =
69             "/data/server_configurable_flags/reset_flags";
70 
71     private static final String SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX = "^[\\w\\.\\-@:]*$";
72 
73     private static final String SYSTEM_PROPERTY_INVALID_SUBSTRING = "..";
74 
75     private static final int SYSTEM_PROPERTY_MAX_LENGTH = 92;
76 
77     // experiment flags added to Global.Settings(before new "Config" provider table is available)
78     // will be added under this category.
79     private static final String GLOBAL_SETTINGS_CATEGORY = "global_settings";
80 
81     // Add the global setting you want to push to native level as experiment flag into this list.
82     //
83     // NOTE: please grant write permission system property prefix
84     // with format persist.device_config.global_settings.[flag_name] in system_server.te and grant
85     // read permission in the corresponding .te file your feature belongs to.
86     @VisibleForTesting
87     static final String[] sGlobalSettings = new String[] {
88             Settings.Global.NATIVE_FLAGS_HEALTH_CHECK_ENABLED,
89     };
90 
91     // TODO(b/282593625): Move this constant to DeviceConfig module
92     private static final String NAMESPACE_TETHERING_U_OR_LATER_NATIVE =
93             "tethering_u_or_later_native";
94 
95     // All the flags under the listed DeviceConfig scopes will be synced to native level.
96     //
97     // NOTE: please grant write permission system property prefix
98     // with format persist.device_config.[device_config_scope]. in system_server.te and grant read
99     // permission in the corresponding .te file your feature belongs to.
100     @VisibleForTesting
101     static final String[] sDeviceConfigScopes = new String[] {
102         DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
103         DeviceConfig.NAMESPACE_CAMERA_NATIVE,
104         DeviceConfig.NAMESPACE_CONFIGURATION,
105         DeviceConfig.NAMESPACE_CONNECTIVITY,
106         DeviceConfig.NAMESPACE_EDGETPU_NATIVE,
107         DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT,
108         DeviceConfig.NAMESPACE_INTELLIGENCE_CONTENT_SUGGESTIONS,
109         DeviceConfig.NAMESPACE_LMKD_NATIVE,
110         DeviceConfig.NAMESPACE_MEDIA_NATIVE,
111         DeviceConfig.NAMESPACE_MGLRU_NATIVE,
112         DeviceConfig.NAMESPACE_NETD_NATIVE,
113         DeviceConfig.NAMESPACE_NNAPI_NATIVE,
114         DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT,
115         DeviceConfig.NAMESPACE_REMOTE_KEY_PROVISIONING_NATIVE,
116         DeviceConfig.NAMESPACE_RUNTIME_NATIVE,
117         DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT,
118         DeviceConfig.NAMESPACE_STATSD_NATIVE,
119         DeviceConfig.NAMESPACE_STATSD_NATIVE_BOOT,
120         DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
121         DeviceConfig.NAMESPACE_SURFACE_FLINGER_NATIVE_BOOT,
122         DeviceConfig.NAMESPACE_SWCODEC_NATIVE,
123         DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE,
124         DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT,
125         DeviceConfig.NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE,
126         DeviceConfig.NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT,
127         DeviceConfig.NAMESPACE_MEMORY_SAFETY_NATIVE_BOOT,
128         DeviceConfig.NAMESPACE_MEMORY_SAFETY_NATIVE,
129         DeviceConfig.NAMESPACE_HDMI_CONTROL,
130         NAMESPACE_TETHERING_U_OR_LATER_NATIVE
131     };
132 
133     // All the aconfig flags under the listed DeviceConfig scopes will be synced to native level.
134     // The list is sorted.
135     @VisibleForTesting
136     static final String[] sDeviceConfigAconfigScopes = new String[] {
137         "accessibility",
138         "android_core_networking",
139         "android_stylus",
140         "aoc",
141         "app_widgets",
142         "arc_next",
143         "art_mainline",
144         "art_performance",
145         "attack_tools",
146         "avic",
147         "biometrics",
148         "biometrics_framework",
149         "biometrics_integration",
150         "bluetooth",
151         "brownout_mitigation_audio",
152         "brownout_mitigation_modem",
153         "build",
154         "camera_hal",
155         "camera_platform",
156         "car_framework",
157         "car_perception",
158         "car_security",
159         "car_telemetry",
160         "codec_fwk",
161         "companion",
162         "com_android_adbd",
163         "content_protection",
164         "context_hub",
165         "core_experiments_team_internal",
166         "core_graphics",
167         "core_libraries",
168         "crumpet",
169         "dck_framework",
170         "devoptions_settings",
171         "game",
172         "gpu",
173         "haptics",
174         "hardware_backed_security_mainline",
175         "input",
176         "llvm_and_toolchains",
177         "lse_desktop_experience",
178         "machine_learning",
179         "mainline_modularization",
180         "mainline_sdk",
181         "make_pixel_haptics",
182         "media_audio",
183         "media_drm",
184         "media_reliability",
185         "media_solutions",
186         "media_tv",
187         "nearby",
188         "nfc",
189         "pdf_viewer",
190         "perfetto",
191         "pixel_audio_android",
192         "pixel_biometrics_face",
193         "pixel_bluetooth",
194         "pixel_connectivity_gps",
195         "pixel_continuity",
196         "pixel_sensors",
197         "pixel_system_sw_video",
198         "pixel_watch",
199         "platform_compat",
200         "platform_security",
201         "pmw",
202         "power",
203         "preload_safety",
204         "printing",
205         "privacy_infra_policy",
206         "resource_manager",
207         "responsible_apis",
208         "rust",
209         "safety_center",
210         "sensors",
211         "spoon",
212         "statsd",
213         "system_performance",
214         "system_sw_touch",
215         "system_sw_usb",
216         "test_suites",
217         "text",
218         "threadnetwork",
219         "treble",
220         "tv_system_ui",
221         "usb",
222         "vibrator",
223         "virtual_devices",
224         "virtualization",
225         "wallet_integration",
226         "wear_calling_messaging",
227         "wear_connectivity",
228         "wear_esim_carriers",
229         "wear_frameworks",
230         "wear_health_services",
231         "wear_media",
232         "wear_offload",
233         "wear_security",
234         "wear_system_health",
235         "wear_systems",
236         "wear_sysui",
237         "window_surfaces",
238         "windowing_frontend",
239     };
240 
241     public static final String NAMESPACE_REBOOT_STAGING = "staged";
242     public static final String NAMESPACE_REBOOT_STAGING_DELIMITER = "*";
243 
244     public static final String NAMESPACE_LOCAL_OVERRIDES = "device_config_overrides";
245 
246     private final String[] mGlobalSettings;
247 
248     private final String[] mDeviceConfigScopes;
249 
250     private final String[] mDeviceConfigAconfigScopes;
251 
252     private final ContentResolver mContentResolver;
253 
254     @VisibleForTesting
SettingsToPropertiesMapper(ContentResolver contentResolver, String[] globalSettings, String[] deviceConfigScopes, String[] deviceConfigAconfigScopes)255     protected SettingsToPropertiesMapper(ContentResolver contentResolver,
256             String[] globalSettings,
257             String[] deviceConfigScopes,
258             String[] deviceConfigAconfigScopes) {
259         mContentResolver = contentResolver;
260         mGlobalSettings = globalSettings;
261         mDeviceConfigScopes = deviceConfigScopes;
262         mDeviceConfigAconfigScopes = deviceConfigAconfigScopes;
263     }
264 
265     @VisibleForTesting
updatePropertiesFromSettings()266     void updatePropertiesFromSettings() {
267         for (String globalSetting : mGlobalSettings) {
268             Uri settingUri = Settings.Global.getUriFor(globalSetting);
269             String propName = makePropertyName(GLOBAL_SETTINGS_CATEGORY, globalSetting);
270             if (settingUri == null) {
271                 logErr("setting uri is null for globalSetting " + globalSetting);
272                 continue;
273             }
274             if (propName == null) {
275                 logErr("invalid prop name for globalSetting " + globalSetting);
276                 continue;
277             }
278 
279             ContentObserver co = new ContentObserver(null) {
280                 @Override
281                 public void onChange(boolean selfChange) {
282                     updatePropertyFromSetting(globalSetting, propName);
283                 }
284             };
285 
286             // only updating on starting up when no native flags reset is performed during current
287             // booting.
288             if (!isNativeFlagsResetPerformed()) {
289                 updatePropertyFromSetting(globalSetting, propName);
290             }
291             mContentResolver.registerContentObserver(settingUri, false, co);
292         }
293 
294         for (String deviceConfigScope : mDeviceConfigScopes) {
295             DeviceConfig.addOnPropertiesChangedListener(
296                     deviceConfigScope,
297                     AsyncTask.THREAD_POOL_EXECUTOR,
298                     (DeviceConfig.Properties properties) -> {
299                         String scope = properties.getNamespace();
300                         for (String key : properties.getKeyset()) {
301                             String propertyName = makePropertyName(scope, key);
302                             if (propertyName == null) {
303                                 logErr("unable to construct system property for " + scope + "/"
304                                         + key);
305                                 return;
306                             }
307                             setProperty(propertyName, properties.getString(key, null));
308 
309                             // for legacy namespaces, they can also be used for trunk stable
310                             // purposes. so push flag also into trunk stable slot in sys prop,
311                             // later all legacy usage will be refactored and the sync to old
312                             // sys prop slot can be removed.
313                             String aconfigPropertyName = makeAconfigFlagPropertyName(scope, key);
314                             if (aconfigPropertyName == null) {
315                                 logErr("unable to construct system property for " + scope + "/"
316                                         + key);
317                                 return;
318                             }
319                             setProperty(aconfigPropertyName, properties.getString(key, null));
320                         }
321                     });
322         }
323 
324         for (String deviceConfigAconfigScope : mDeviceConfigAconfigScopes) {
325             DeviceConfig.addOnPropertiesChangedListener(
326                     deviceConfigAconfigScope,
327                     AsyncTask.THREAD_POOL_EXECUTOR,
328                     (DeviceConfig.Properties properties) -> {
329                         String scope = properties.getNamespace();
330                         for (String key : properties.getKeyset()) {
331                             String aconfigPropertyName = makeAconfigFlagPropertyName(scope, key);
332                             if (aconfigPropertyName == null) {
333                                 logErr("unable to construct system property for " + scope + "/"
334                                         + key);
335                                 return;
336                             }
337                             setProperty(aconfigPropertyName, properties.getString(key, null));
338                         }
339                     });
340         }
341 
342         // add sys prop sync callback for staged flag values
343         DeviceConfig.addOnPropertiesChangedListener(
344             NAMESPACE_REBOOT_STAGING,
345             AsyncTask.THREAD_POOL_EXECUTOR,
346             (DeviceConfig.Properties properties) -> {
347 
348               for (String flagName : properties.getKeyset()) {
349                   String flagValue = properties.getString(flagName, null);
350                   if (flagName == null || flagValue == null) {
351                       continue;
352                   }
353 
354                   int idx = flagName.indexOf(NAMESPACE_REBOOT_STAGING_DELIMITER);
355                   if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
356                       logErr("invalid staged flag: " + flagName);
357                       continue;
358                   }
359 
360                   String actualNamespace = flagName.substring(0, idx);
361                   String actualFlagName = flagName.substring(idx+1);
362                   String propertyName = "next_boot." + makeAconfigFlagPropertyName(
363                       actualNamespace, actualFlagName);
364 
365                   setProperty(propertyName, flagValue);
366               }
367 
368               // send prop stage request to new storage
369               if (enableAconfigStorageDaemon()) {
370                   stageFlagsInNewStorage(properties);
371               }
372 
373         });
374 
375         // add prop sync callback for flag local overrides
376         DeviceConfig.addOnPropertiesChangedListener(
377             NAMESPACE_LOCAL_OVERRIDES,
378             AsyncTask.THREAD_POOL_EXECUTOR,
379             (DeviceConfig.Properties properties) -> {
380                 if (enableAconfigStorageDaemon()) {
381                     setLocalOverridesInNewStorage(properties);
382                 }
383         });
384     }
385 
386     /**
387      * apply flag local override in aconfig new storage
388      * @param requests: request proto output stream
389      * @return aconfigd socket return as proto input stream
390      */
sendAconfigdRequests(ProtoOutputStream requests)391     static ProtoInputStream sendAconfigdRequests(ProtoOutputStream requests) {
392         // connect to aconfigd socket
393         LocalSocket client = new LocalSocket();
394         try{
395             client.connect(new LocalSocketAddress(
396                 "aconfigd", LocalSocketAddress.Namespace.RESERVED));
397             Slog.d(TAG, "connected to aconfigd socket");
398         } catch (IOException ioe) {
399             logErr("failed to connect to aconfigd socket", ioe);
400             return null;
401         }
402 
403         DataInputStream inputStream = null;
404         DataOutputStream outputStream = null;
405         try {
406             inputStream = new DataInputStream(client.getInputStream());
407             outputStream = new DataOutputStream(client.getOutputStream());
408         } catch (IOException ioe) {
409             logErr("failed to get local socket iostreams", ioe);
410             return null;
411         }
412 
413         // send requests
414         try {
415             byte[] requests_bytes = requests.getBytes();
416             outputStream.writeInt(requests_bytes.length);
417             outputStream.write(requests_bytes, 0, requests_bytes.length);
418             Slog.d(TAG, "flag override requests sent to aconfigd");
419         } catch (IOException ioe) {
420             logErr("failed to send requests to aconfigd", ioe);
421             return null;
422         }
423 
424         // read return
425         try {
426             int num_bytes = inputStream.readInt();
427             ProtoInputStream returns = new ProtoInputStream(inputStream);
428             Slog.d(TAG, "received " + num_bytes + " bytes back from aconfigd");
429             return returns;
430         } catch (IOException ioe) {
431             logErr("failed to read requests return from aconfigd", ioe);
432             return null;
433         }
434     }
435 
436     /**
437      * serialize a flag override request
438      * @param proto
439      */
writeFlagOverrideRequest( ProtoOutputStream proto, String packageName, String flagName, String flagValue, boolean isLocal)440     static void writeFlagOverrideRequest(
441         ProtoOutputStream proto, String packageName, String flagName, String flagValue,
442         boolean isLocal) {
443       long msgsToken = proto.start(StorageRequestMessages.MSGS);
444       long msgToken = proto.start(StorageRequestMessage.FLAG_OVERRIDE_MESSAGE);
445       proto.write(StorageRequestMessage.FlagOverrideMessage.PACKAGE_NAME, packageName);
446       proto.write(StorageRequestMessage.FlagOverrideMessage.FLAG_NAME, flagName);
447       proto.write(StorageRequestMessage.FlagOverrideMessage.FLAG_VALUE, flagValue);
448       proto.write(StorageRequestMessage.FlagOverrideMessage.IS_LOCAL, isLocal);
449       proto.end(msgToken);
450       proto.end(msgsToken);
451     }
452 
453     /**
454      * deserialize a flag input proto stream and log
455      * @param proto
456      */
parseAndLogAconfigdReturn(ProtoInputStream proto)457     static void parseAndLogAconfigdReturn(ProtoInputStream proto) throws IOException {
458         while (true) {
459           switch (proto.nextField()) {
460             case (int) StorageReturnMessages.MSGS:
461               long msgsToken = proto.start(StorageReturnMessages.MSGS);
462               switch (proto.nextField()) {
463                 case (int) StorageReturnMessage.FLAG_OVERRIDE_MESSAGE:
464                   Slog.d(TAG, "successfully handled override requests");
465                   long msgToken = proto.start(StorageReturnMessage.FLAG_OVERRIDE_MESSAGE);
466                   proto.end(msgToken);
467                   break;
468                 case (int) StorageReturnMessage.ERROR_MESSAGE:
469                   String errmsg = proto.readString(StorageReturnMessage.ERROR_MESSAGE);
470                   Slog.d(TAG, "override request failed: " + errmsg);
471                   break;
472                 case ProtoInputStream.NO_MORE_FIELDS:
473                   break;
474                 default:
475                   logErr("invalid message type, expecting only flag override return or error message");
476                   break;
477               }
478               proto.end(msgsToken);
479               break;
480             case ProtoInputStream.NO_MORE_FIELDS:
481               return;
482             default:
483               logErr("invalid message type, expect storage return message");
484               break;
485           }
486         }
487     }
488 
489     /**
490      * apply flag local override in aconfig new storage
491      * @param props
492      */
setLocalOverridesInNewStorage(DeviceConfig.Properties props)493     static void setLocalOverridesInNewStorage(DeviceConfig.Properties props) {
494         int num_requests = 0;
495         ProtoOutputStream requests = new ProtoOutputStream();
496         for (String flagName : props.getKeyset()) {
497             String flagValue = props.getString(flagName, null);
498             if (flagName == null || flagValue == null) {
499                 continue;
500             }
501 
502             int idx = flagName.indexOf(":");
503             if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
504                 logErr("invalid local flag override: " + flagName);
505                 continue;
506             }
507             String actualNamespace = flagName.substring(0, idx);
508             String fullFlagName = flagName.substring(idx+1);
509             idx = fullFlagName.lastIndexOf(".");
510             if (idx == -1) {
511               logErr("invalid flag name: " + fullFlagName);
512               continue;
513             }
514             String packageName = fullFlagName.substring(0, idx);
515             String realFlagName = fullFlagName.substring(idx+1);
516             writeFlagOverrideRequest(requests, packageName, realFlagName, flagValue, true);
517             ++num_requests;
518         }
519 
520         if (num_requests == 0) {
521           return;
522         }
523 
524         // send requests to aconfigd and obtain the return byte buffer
525         ProtoInputStream returns = sendAconfigdRequests(requests);
526 
527         // deserialize back using proto input stream
528         try {
529           parseAndLogAconfigdReturn(returns);
530         } catch (IOException ioe) {
531             logErr("failed to parse aconfigd return", ioe);
532         }
533     }
534 
start(ContentResolver contentResolver)535     public static SettingsToPropertiesMapper start(ContentResolver contentResolver) {
536         SettingsToPropertiesMapper mapper =  new SettingsToPropertiesMapper(
537                 contentResolver,
538                 sGlobalSettings,
539                 sDeviceConfigScopes,
540                 sDeviceConfigAconfigScopes);
541         mapper.updatePropertiesFromSettings();
542         return mapper;
543     }
544 
545     /**
546      * If native level flags reset has been performed as an attempt to recover from a crash loop
547      * during current device booting.
548      * @return
549      */
isNativeFlagsResetPerformed()550     public static boolean isNativeFlagsResetPerformed() {
551         String value = SystemProperties.get(RESET_PERFORMED_PROPERTY);
552         return "true".equals(value);
553     }
554 
555     /**
556      * return an array of native flag categories under which flags got reset during current device
557      * booting.
558      * @return
559      */
getResetNativeCategories()560     public static @NonNull String[] getResetNativeCategories() {
561         if (!isNativeFlagsResetPerformed()) {
562             return new String[0];
563         }
564 
565         String content = getResetFlagsFileContent();
566         if (TextUtils.isEmpty(content)) {
567             return new String[0];
568         }
569 
570         String[] property_names = content.split(";");
571         HashSet<String> categories = new HashSet<>();
572         for (String property_name : property_names) {
573             String[] segments = property_name.split("\\.");
574             if (segments.length < 3) {
575                 logErr("failed to extract category name from property " + property_name);
576                 continue;
577             }
578             categories.add(segments[2]);
579         }
580         return categories.toArray(new String[0]);
581     }
582 
583     /**
584      * system property name constructing rule: "persist.device_config.[category_name].[flag_name]".
585      * If the name contains invalid characters or substrings for system property name,
586      * will return null.
587      * @param categoryName
588      * @param flagName
589      * @return
590      */
591     @VisibleForTesting
makePropertyName(String categoryName, String flagName)592     static String makePropertyName(String categoryName, String flagName) {
593         String propertyName = SYSTEM_PROPERTY_PREFIX + categoryName + "." + flagName;
594 
595         if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX)
596                 || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) {
597             return null;
598         }
599 
600         return propertyName;
601     }
602 
603 
604     /**
605      * stage flags in aconfig new storage
606      * @param propsToStage
607      */
608     @VisibleForTesting
stageFlagsInNewStorage(DeviceConfig.Properties props)609     static void stageFlagsInNewStorage(DeviceConfig.Properties props) {
610         // write aconfigd requests proto to proto output stream
611         int num_requests = 0;
612         ProtoOutputStream requests = new ProtoOutputStream();
613         for (String flagName : props.getKeyset()) {
614             String flagValue = props.getString(flagName, null);
615             if (flagName == null || flagValue == null) {
616                 continue;
617             }
618 
619             int idx = flagName.indexOf("*");
620             if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
621                 logErr("invalid local flag override: " + flagName);
622                 continue;
623             }
624             String actualNamespace = flagName.substring(0, idx);
625             String fullFlagName = flagName.substring(idx+1);
626 
627             idx = fullFlagName.lastIndexOf(".");
628             if (idx == -1) {
629                 logErr("invalid flag name: " + fullFlagName);
630                 continue;
631             }
632             String packageName = fullFlagName.substring(0, idx);
633             String realFlagName = fullFlagName.substring(idx+1);
634             writeFlagOverrideRequest(requests, packageName, realFlagName, flagValue, false);
635             ++num_requests;
636         }
637 
638         if (num_requests == 0) {
639           return;
640         }
641 
642         // send requests to aconfigd and obtain the return
643         ProtoInputStream returns = sendAconfigdRequests(requests);
644 
645         // deserialize back using proto input stream
646         try {
647             parseAndLogAconfigdReturn(returns);
648         } catch (IOException ioe) {
649             logErr("failed to parse aconfigd return", ioe);
650         }
651     }
652 
653     /**
654      * system property name constructing rule for aconfig flags:
655      * "persist.device_config.aconfig_flags.[category_name].[flag_name]".
656      * If the name contains invalid characters or substrings for system property name,
657      * will return null.
658      * @param categoryName
659      * @param flagName
660      * @return
661      */
662     @VisibleForTesting
makeAconfigFlagPropertyName(String categoryName, String flagName)663     static String makeAconfigFlagPropertyName(String categoryName, String flagName) {
664         String propertyName = SYSTEM_PROPERTY_PREFIX + "aconfig_flags." +
665                               categoryName + "." + flagName;
666 
667         if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX)
668                 || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) {
669             return null;
670         }
671 
672         return propertyName;
673     }
674 
setProperty(String key, String value)675     private void setProperty(String key, String value) {
676         // Check if need to clear the property
677         if (value == null) {
678             // It's impossible to remove system property, therefore we check previous value to
679             // avoid setting an empty string if the property wasn't set.
680             if (TextUtils.isEmpty(SystemProperties.get(key))) {
681                 return;
682             }
683             value = "";
684         } else if (value.length() > SYSTEM_PROPERTY_MAX_LENGTH) {
685             logErr("key=" + key + " value=" + value + " exceeds system property max length.");
686             return;
687         }
688 
689         try {
690             SystemProperties.set(key, value);
691         } catch (Exception e) {
692             // Failure to set a property can be caused by SELinux denial. This usually indicates
693             // that the property wasn't allowlisted in sepolicy.
694             // No need to report it on all user devices, only on debug builds.
695             logErr("Unable to set property " + key + " value '" + value + "'", e);
696         }
697     }
698 
logErr(String msg, Exception e)699     private static void logErr(String msg, Exception e) {
700         if (Build.IS_DEBUGGABLE) {
701             Slog.wtf(TAG, msg, e);
702         } else {
703             Slog.e(TAG, msg, e);
704         }
705     }
706 
logErr(String msg)707     private static void logErr(String msg) {
708         if (Build.IS_DEBUGGABLE) {
709             Slog.wtf(TAG, msg);
710         } else {
711             Slog.e(TAG, msg);
712         }
713     }
714 
715     @VisibleForTesting
getResetFlagsFileContent()716     static String getResetFlagsFileContent() {
717         String content = null;
718         try {
719             File reset_flag_file = new File(RESET_RECORD_FILE_PATH);
720             BufferedReader br = new BufferedReader(new FileReader(reset_flag_file));
721             content = br.readLine();
722 
723             br.close();
724         } catch (IOException ioe) {
725             logErr("failed to read file " + RESET_RECORD_FILE_PATH, ioe);
726         }
727         return content;
728     }
729 
730     @VisibleForTesting
updatePropertyFromSetting(String settingName, String propName)731     void updatePropertyFromSetting(String settingName, String propName) {
732         String settingValue = Settings.Global.getString(mContentResolver, settingName);
733         setProperty(propName, settingValue);
734     }
735 }
736