• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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 package com.android.compatibility.common.deviceinfo;
17 
18 import android.icu.util.ULocale;
19 import android.icu.util.VersionInfo;
20 import android.util.Log;
21 import androidx.annotation.Nullable;
22 import com.android.compatibility.common.util.DeviceInfoStore;
23 import com.google.common.base.Strings;
24 
25 import java.io.File;
26 import java.io.FileInputStream;
27 import java.io.IOException;
28 import java.nio.MappedByteBuffer;
29 import java.nio.channels.FileChannel;
30 import java.nio.file.Files;
31 import java.nio.file.Path;
32 import java.nio.file.Paths;
33 import java.security.MessageDigest;
34 import java.security.NoSuchAlgorithmException;
35 import java.util.ArrayList;
36 import java.util.Arrays;
37 import java.util.List;
38 import java.util.regex.Matcher;
39 import java.util.regex.Pattern;
40 import java.util.stream.Collectors;
41 import java.util.stream.Stream;
42 
43 /**
44  * Locale device info collector.
45  */
46 public final class LocaleDeviceInfo extends DeviceInfo {
47     private static final String TAG = "LocaleDeviceInfo";
48 
49     private static final Pattern HYPHEN_BINARY_PATTERN = Pattern.compile("hyph-(.*?).hyb");
50     private static final String HYPHEN_BINARY_LOCATION = "/system/usr/hyphen-data/";
51 
52     @Override
collectDeviceInfo(DeviceInfoStore store)53     protected void collectDeviceInfo(DeviceInfoStore store) throws Exception {
54         List<String> locales = Arrays.asList(
55                 getInstrumentation().getContext().getAssets().getLocales());
56         if (locales.isEmpty()) {
57             // default locale
58             locales.add("en_US");
59         }
60         store.addListResult("locale", locales);
61 
62         List<String> icuLocales = Arrays.stream(ULocale.getAvailableLocales())
63             .map((uLocale -> uLocale.toLanguageTag()))
64             .collect(Collectors.toList());
65         if (icuLocales.isEmpty()) {
66             // default locale
67             icuLocales.add(ULocale.US.toLanguageTag());
68         }
69         store.addListResult("icu_locale", icuLocales);
70 
71         // Collect hyphenation supported locale
72         List<String> hyphenLocalesList = new ArrayList<>();
73         // Retrieve locale from the file name of binary
74         try (Stream<Path> stream = Files.walk(Paths.get(HYPHEN_BINARY_LOCATION))) {
75             hyphenLocalesList = stream
76                     .filter(file -> !Files.isDirectory(file))
77                     .map(Path::getFileName)
78                     .map(Path::toString)
79                     .filter(HYPHEN_BINARY_PATTERN.asPredicate())
80                     .map(s -> {
81                         Matcher matcher = HYPHEN_BINARY_PATTERN.matcher(s);
82                         return matcher.find() ? Strings.nullToEmpty(matcher.group(1)) : "";
83                     })
84                     .sorted()
85                     .collect(Collectors.toList());
86         } catch (IOException e) {
87             Log.w(TAG,"Hyphenation binary folder is not exist" , e);
88         }
89         store.addListResult("hyphenation_locale", hyphenLocalesList);
90 
91         collectLocaleDataFilesInfo(store);
92     }
93 
94     /**
95      * Collect the fingerprints of ICU data files. On AOSP build, there are only 2 data files.
96      * The example paths are /apex/com.android.tzdata/etc/icu/icu_tzdata.dat and
97      * /apex/com.android.i18n/etc/icu/icudt65l.dat
98      */
collectLocaleDataFilesInfo(DeviceInfoStore store)99     private void collectLocaleDataFilesInfo(DeviceInfoStore store) throws IOException {
100         int icuVersion = VersionInfo.ICU_VERSION.getMajor();
101         File[] fixedDatPaths = new File[] {
102                 new File("/apex/com.android.tzdata/etc/icu/icu_tzdata.dat"),
103                 new File("/apex/com.android.i18n/etc/icu/icudt" + icuVersion + "l.dat"),
104         };
105 
106         // This property has been deprecated since Android 12. The property will not work if this
107         // app targets SDK level 31 or higher. Thus, we add the above fixedDatPaths in case that
108         // the property is not working. When this comment was written, this CTS app still targets
109         // SDK level 23.
110         String prop = System.getProperty("android.icu.impl.ICUBinary.dataPath");
111         store.startArray("icu_data_file_info");
112 
113         List<File> datFiles = new ArrayList<>();
114         if (prop != null) {
115             String[] dataDirs = prop.split(":");
116             // List all ".dat" files in the directories.
117             datFiles = Arrays.stream(dataDirs)
118                 .filter((dir) -> dir != null && !dir.isEmpty())
119                 .map((dir) -> new File(dir))
120                 .filter((f) -> f.canRead() && f.isDirectory())
121                 .map((f) -> f.listFiles())
122                 .filter((files) -> files != null)
123                 .flatMap(files -> Stream.of(files))
124                 .collect(Collectors.toList());
125         }
126 
127         datFiles.addAll(Arrays.asList(fixedDatPaths));
128 
129         // Keep the readable paths only.
130         datFiles = datFiles.stream()
131             .distinct()
132             .filter((f) -> f != null && f.canRead() && f.getName().endsWith(".dat"))
133             .collect(Collectors.toList());
134 
135         for (File datFile : datFiles) {
136             String sha256Hash = sha256(datFile);
137 
138             store.startGroup();
139             store.addResult("file_path", datFile.getPath());
140             // Still store the null hash to indicate an error occurring when obtaining the hash.
141             store.addResult("file_sha256", sha256Hash);
142             store.endGroup();
143         }
144         store.endArray();
145     }
146 
sha256(File file)147     public static @Nullable String sha256(File file) {
148         try (FileInputStream in = new FileInputStream(file);
149             FileChannel fileChannel = in.getChannel()) {
150 
151             MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0,
152                 fileChannel.size());
153 
154             MessageDigest md = MessageDigest.getInstance("SHA-256");
155             md.update(mappedByteBuffer);
156 
157             byte[] digest = md.digest();
158             StringBuilder sb = new StringBuilder(digest.length * 2);
159             for(int i = 0; i < digest.length; i++){
160                 sb.append(Character.forDigit((digest[i] >> 4) & 0xF, 16));
161                 sb.append(Character.forDigit((digest[i] & 0xF), 16));
162             }
163             return sb.toString();
164         } catch (IOException | NoSuchAlgorithmException e) {
165             Log.w(TAG, String.format("Can't obtain the hash of file: %s", file), e);
166             return null;
167         }
168     }
169 }
170