1 /*
2  * Copyright (C) 2023 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 android.transparency.test.app;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static com.google.common.truth.Truth.assertWithMessage;
21 
22 import android.content.Context;
23 import android.os.Bundle;
24 import android.transparency.BinaryTransparencyManager;
25 import android.util.Log;
26 
27 import androidx.test.platform.app.InstrumentationRegistry;
28 import androidx.test.runner.AndroidJUnit4;
29 
30 import com.android.internal.os.IBinaryTransparencyService.AppInfo;
31 
32 import org.junit.Before;
33 import org.junit.Test;
34 import org.junit.runner.RunWith;
35 
36 import java.util.ArrayList;
37 import java.util.HashSet;
38 import java.util.HexFormat;
39 import java.util.Set;
40 import java.util.stream.Collectors;
41 
42 @RunWith(AndroidJUnit4.class)
43 public class BinaryTransparencyTest {
44     private static final String TAG = "BinaryTransparencyTest";
45 
46     private BinaryTransparencyManager mBt;
47 
48     @Before
setUp()49     public void setUp() {
50         Context context = InstrumentationRegistry.getInstrumentation().getContext();
51         mBt = context.getSystemService(BinaryTransparencyManager.class);
52     }
53 
54     @Test
testCollectAllApexInfo()55     public void testCollectAllApexInfo() {
56         // Prepare the expectation received from host's shell command
57         Bundle args = InstrumentationRegistry.getArguments();
58         assertThat(args).isNotNull();
59         int number = Integer.valueOf(args.getString("apex-number"));
60         assertThat(number).isGreaterThan(0);
61         var expectedApexNames = new ArrayList<String>();
62         for (var i = 0; i < number; i++) {
63             String moduleName = args.getString("apex-" + Integer.toString(i));
64             expectedApexNames.add(moduleName);
65         }
66         assertThat(expectedApexNames).containsNoDuplicates();
67 
68         // Action
69         var apexInfoList = mBt.collectAllApexInfo(/* includeTestOnly */ true);
70 
71         // Verify actual apex names
72         var actualApexesNames = apexInfoList.stream().map((apex) -> apex.moduleName)
73                 .collect(Collectors.toList());
74         assertThat(actualApexesNames).containsExactlyElementsIn(expectedApexNames);
75 
76         // Perform more valitidy checks
77         var digestsSeen = new HashSet<String>();
78         var hexFormatter = HexFormat.of();
79         for (var apex : apexInfoList) {
80             Log.d(TAG, "Verifying " + apex.packageName + " / " + apex.moduleName);
81 
82             assertThat(apex.longVersion).isGreaterThan(0);
83             assertThat(apex.digestAlgorithm).isGreaterThan(0);
84             assertThat(apex.signerDigests).asList().containsNoneOf(null, "");
85 
86             assertThat(apex.digest).isNotNull();
87             String digestHex = hexFormatter.formatHex(apex.digest);
88             boolean isNew = digestsSeen.add(digestHex);
89             assertWithMessage(
90                     "Digest should be unique, but received a dup: " + digestHex)
91                     .that(isNew).isTrue();
92         }
93     }
94 
95     @Test
testCollectAllUpdatedPreloadInfo()96     public void testCollectAllUpdatedPreloadInfo() {
97         var preloadInfoList = mBt.collectAllUpdatedPreloadInfo(new Bundle());
98         assertThat(preloadInfoList).isNotEmpty();  // because we just installed from the host side
99         AppInfo updatedPreload = null;
100         for (var preload : preloadInfoList) {
101             Log.d(TAG, "Received " + preload.packageName);
102             if (preload.packageName.equals("com.android.egg")) {
103                 assertWithMessage("Received the same package").that(updatedPreload).isNull();
104                 updatedPreload = preload;
105             }
106         }
107 
108         // Verify
109         assertThat(updatedPreload.longVersion).isGreaterThan(0);
110         assertThat(updatedPreload.digestAlgorithm).isGreaterThan(0);
111         assertThat(updatedPreload.digest).isNotEmpty();
112         assertThat(updatedPreload.mbaStatus).isEqualTo(/* MBA_STATUS_UPDATED_PRELOAD */ 2);
113         assertThat(updatedPreload.signerDigests).asList().containsNoneOf(null, "");
114     }
115 
116     @Test
testCollectAllSilentInstalledMbaInfo()117     public void testCollectAllSilentInstalledMbaInfo() {
118         // Action
119         var appInfoList = mBt.collectAllSilentInstalledMbaInfo(new Bundle());
120 
121         // Verify
122         assertThat(appInfoList).isNotEmpty();  // because we just installed from the host side
123 
124         var expectedAppNames = Set.of("com.android.apkverity", "com.android.egg");
125         var actualAppNames = appInfoList.stream().map((appInfo) -> appInfo.packageName)
126                 .collect(Collectors.toList());
127         assertThat(actualAppNames).containsAtLeastElementsIn(expectedAppNames);
128 
129         var actualSplitNames = new ArrayList<String>();
130         for (var appInfo : appInfoList) {
131             Log.d(TAG, "Received " + appInfo.packageName + " as a silent install");
132             if (expectedAppNames.contains(appInfo.packageName)) {
133                 assertThat(appInfo.longVersion).isGreaterThan(0);
134                 assertThat(appInfo.digestAlgorithm).isGreaterThan(0);
135                 assertThat(appInfo.digest).isNotEmpty();
136                 assertThat(appInfo.mbaStatus).isEqualTo(/* MBA_STATUS_NEW_INSTALL */ 3);
137                 assertThat(appInfo.signerDigests).asList().containsNoneOf(null, "");
138 
139                 if (appInfo.splitName != null) {
140                     actualSplitNames.add(appInfo.splitName);
141                 }
142             }
143         }
144         assertThat(actualSplitNames).containsExactly("feature_x");  // Name of ApkVerityTestAppSplit
145     }
146 }
147