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.util.Log;
20 import androidx.annotation.Nullable;
21 import com.android.compatibility.common.util.DeviceInfoStore;
22 
23 import java.io.File;
24 import java.io.FileInputStream;
25 import java.io.IOException;
26 import java.nio.MappedByteBuffer;
27 import java.nio.channels.FileChannel;
28 import java.security.MessageDigest;
29 import java.security.NoSuchAlgorithmException;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.List;
33 import java.util.stream.Collectors;
34 
35 /**
36  * Locale device info collector.
37  */
38 public final class LocaleDeviceInfo extends DeviceInfo {
39     private static final String TAG = "LocaleDeviceInfo";
40 
41     @Override
collectDeviceInfo(DeviceInfoStore store)42     protected void collectDeviceInfo(DeviceInfoStore store) throws Exception {
43         List<String> locales = Arrays.asList(
44                 getInstrumentation().getContext().getAssets().getLocales());
45         if (locales.isEmpty()) {
46             // default locale
47             locales.add("en_US");
48         }
49         store.addListResult("locale", locales);
50 
51         List<String> icuLocales = Arrays.stream(ULocale.getAvailableLocales())
52             .map((uLocale -> uLocale.toLanguageTag()))
53             .collect(Collectors.toList());
54         if (icuLocales.isEmpty()) {
55             // default locale
56             icuLocales.add(ULocale.US.toLanguageTag());
57         }
58         store.addListResult("icu_locale", icuLocales);
59 
60         collectLocaleDataFilesInfo(store);
61     }
62 
63     /**
64      * Collect the fingerprints of ICU data files. On AOSP build, there are only 2 data files.
65      * The example paths are /apex/com.android.tzdata/etc/icu/icu_tzdata.dat and
66      * /apex/com.android.i18n/etc/icu/icudt65l.dat
67      */
collectLocaleDataFilesInfo(DeviceInfoStore store)68     private void collectLocaleDataFilesInfo(DeviceInfoStore store) throws IOException {
69         String prop = System.getProperty("android.icu.impl.ICUBinary.dataPath");
70         store.startArray("icu_data_file_info");
71         if (prop != null) {
72             String[] dataDirs = prop.split(":");
73             // List all readable ".dat" files in the directories.
74             List<File> datFiles = Arrays.stream(dataDirs)
75                 .filter((dir) -> dir != null && !dir.isEmpty())
76                 .map((dir) -> new File(dir))
77                 .filter((f) -> f.canRead() && f.isDirectory())
78                 .map((f) -> f.listFiles())
79                 .filter((files) -> files != null)
80                 .map((files) -> Arrays.asList(files))
81                 .reduce(new ArrayList<>(), (l1, l2) -> {
82                     l1.addAll(l2);
83                     return l1;
84                 })
85                 .stream()
86                 .filter((f) -> f != null && f.canRead() && f.getName().endsWith(".dat"))
87                 .collect(Collectors.toList());
88 
89             for (File datFile : datFiles) {
90                 String sha256Hash = sha256(datFile);
91 
92                 store.startGroup();
93                 store.addResult("file_path", datFile.getPath());
94                 // Still store the null hash to indicate an error occurring when obtaining the hash.
95                 store.addResult("file_sha256", sha256Hash);
96                 store.endGroup();
97             }
98         }
99         store.endArray();
100     }
101 
sha256(File file)102     public static @Nullable String sha256(File file) {
103         try (FileInputStream in = new FileInputStream(file);
104             FileChannel fileChannel = in.getChannel()) {
105 
106             MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0,
107                 fileChannel.size());
108 
109             MessageDigest md = MessageDigest.getInstance("SHA-256");
110             md.update(mappedByteBuffer);
111 
112             byte[] digest = md.digest();
113             StringBuilder sb = new StringBuilder(digest.length * 2);
114             for(int i = 0; i < digest.length; i++){
115                 sb.append(Character.forDigit((digest[i] >> 4) & 0xF, 16));
116                 sb.append(Character.forDigit((digest[i] & 0xF), 16));
117             }
118             return sb.toString();
119         } catch (IOException | NoSuchAlgorithmException e) {
120             Log.w(TAG, String.format("Can't obtain the hash of file: %s", file), e);
121             return null;
122         }
123     }
124 }
125