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