1 /*
2  * Copyright (C) 2016 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 android.signature.cts.intent;
17 
18 import android.content.pm.ApplicationInfo;
19 import android.content.pm.PackageManager;
20 import android.signature.cts.ApiDocumentParser;
21 import android.signature.cts.JDiffClassDescription.JDiffField;
22 import android.signature.cts.VirtualPath;
23 import android.util.Log;
24 
25 import androidx.test.InstrumentationRegistry;
26 import androidx.test.runner.AndroidJUnit4;
27 
28 import com.android.compatibility.common.util.DynamicConfigDeviceSide;
29 
30 import java.io.IOException;
31 import org.junit.Assert;
32 import org.junit.Before;
33 import org.junit.Test;
34 import org.junit.runner.RunWith;
35 
36 import java.io.BufferedReader;
37 import java.io.File;
38 import java.io.FileInputStream;
39 import java.io.InputStreamReader;
40 import java.util.HashSet;
41 import java.util.List;
42 import java.util.Set;
43 
44 /**
45  * Validate that the android intents used by APKs on this device are part of the
46  * platform.
47  */
48 @RunWith(AndroidJUnit4.class)
49 public class IntentTest {
50 
51     private static final String CURRENT_API_RESOURCE = "current.api.gz";
52 
53     private static final String SYSTEM_CURRENT_API_RESOURCE = "system-current.api.gz";
54 
55     private static final String SYSTEM_REMOVED_API_RESOURCE = "system-removed.api.gz";
56 
57     private static final String TAG = IntentTest.class.getSimpleName();
58 
59     private static final File SIGNATURE_TEST_PACKAGES =
60             new File("/data/local/tmp/signature-test-packages");
61     private static final String ANDROID_INTENT_PREFIX = "android.intent.action";
62     private static final String ACTION_LINE_PREFIX = "          Action: ";
63     private static final String MODULE_NAME = "CtsIntentSignatureTestCases";
64 
65     private PackageManager mPackageManager;
66     private Set<String> intentWhitelist;
67 
68     @Before
setupPackageManager()69     public void setupPackageManager() throws Exception {
70       mPackageManager = InstrumentationRegistry.getContext().getPackageManager();
71       intentWhitelist = getIntentWhitelist();
72     }
73 
74     @Test
shouldNotFindUnexpectedIntents()75     public void shouldNotFindUnexpectedIntents() throws Exception {
76         Set<String> platformIntents = lookupPlatformIntents();
77         platformIntents.addAll(intentWhitelist);
78 
79         Set<String> allInvalidIntents = new HashSet<>();
80 
81         Set<String> errors = new HashSet<>();
82         List<ApplicationInfo> packages =
83             mPackageManager.getInstalledApplications(PackageManager.GET_META_DATA);
84         for (ApplicationInfo appInfo : packages) {
85             if (!isSystemApp(appInfo) && !isUpdatedSystemApp(appInfo)) {
86                 // Only examine system apps
87                 continue;
88             }
89             Set<String> invalidIntents = new HashSet<>();
90             Set<String> activeIntents = lookupActiveIntents(appInfo.packageName);
91 
92             for (String activeIntent : activeIntents) {
93               String intent = activeIntent.trim();
94               if (!platformIntents.contains(intent) &&
95                     intent.startsWith(ANDROID_INTENT_PREFIX)) {
96                   invalidIntents.add(activeIntent);
97                   allInvalidIntents.add(activeIntent);
98               }
99             }
100 
101             String error = String.format("Package: %s Invalid Intent: %s",
102                   appInfo.packageName, invalidIntents);
103             if (!invalidIntents.isEmpty()) {
104                 errors.add(error);
105             }
106         }
107 
108         // Log the whitelist line to make it easy to update.
109         for (String intent : allInvalidIntents) {
110            Log.d(TAG, String.format("whitelist.add(\"%s\");", intent));
111         }
112 
113         Assert.assertTrue(errors.toString(), errors.isEmpty());
114     }
115 
lookupPlatformIntents()116     private Set<String> lookupPlatformIntents() throws IOException {
117         Set<String> intents = new HashSet<>();
118         intents.addAll(parse(CURRENT_API_RESOURCE));
119         intents.addAll(parse(SYSTEM_CURRENT_API_RESOURCE));
120         intents.addAll(parse(SYSTEM_REMOVED_API_RESOURCE));
121         return intents;
122     }
123 
parse(String apiResourceName)124     private static Set<String> parse(String apiResourceName) throws IOException {
125 
126         Set<String> androidIntents = new HashSet<>();
127 
128         ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG);
129 
130         VirtualPath.ResourcePath virtualPath =
131                 VirtualPath.get(IntentTest.class.getClassLoader(), apiResourceName);
132         apiDocumentParser.parseAsStream(virtualPath).forEach(
133                 classDescription -> {
134                     for (JDiffField diffField : classDescription.getFieldList()) {
135                         String fieldValue = diffField.getValueString();
136                         if (fieldValue != null) {
137                             fieldValue = fieldValue.replace("\"", "");
138                             if (fieldValue.startsWith(ANDROID_INTENT_PREFIX)) {
139                                 androidIntents.add(fieldValue);
140                             }
141                         }
142                     }
143                 });
144 
145         return androidIntents;
146     }
147 
isSystemApp(ApplicationInfo applicationInfo)148     private static boolean isSystemApp(ApplicationInfo applicationInfo) {
149         return (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
150     }
151 
isUpdatedSystemApp(ApplicationInfo applicationInfo)152     private static boolean isUpdatedSystemApp(ApplicationInfo applicationInfo) {
153         return (applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
154     }
155 
lookupActiveIntents(String packageName)156     private static Set<String> lookupActiveIntents(String packageName) {
157         HashSet<String> activeIntents = new HashSet<>();
158         File dumpsysPackage = new File(SIGNATURE_TEST_PACKAGES, packageName + ".txt");
159         if (!dumpsysPackage.exists() || dumpsysPackage.length() == 0) {
160           throw new RuntimeException("Missing package info: " + dumpsysPackage.getAbsolutePath());
161         }
162         try (
163             BufferedReader in = new BufferedReader(
164                   new InputStreamReader(new FileInputStream(dumpsysPackage)))) {
165             String line;
166             while ((line = in.readLine()) != null) {
167                 if (line.startsWith(ACTION_LINE_PREFIX)) {
168                     String intent = line.substring(
169                           ACTION_LINE_PREFIX.length(), line.length() - 1);
170                     activeIntents.add(intent.replace("\"", ""));
171                 }
172             }
173             return activeIntents;
174         } catch (Exception e) {
175           throw new RuntimeException("While retrieving dumpsys", e);
176         }
177     }
178 
getIntentWhitelist()179     private static Set<String> getIntentWhitelist() throws Exception {
180         Set<String> whitelist = new HashSet<>();
181 
182         DynamicConfigDeviceSide dcds = new DynamicConfigDeviceSide(MODULE_NAME);
183         List<String> intentWhitelist = dcds.getValues("intent_whitelist");
184 
185         // Log the whitelist Intent
186         for (String intent : intentWhitelist) {
187            Log.d(TAG, String.format("whitelist add: %s", intent));
188            whitelist.add(intent);
189         }
190 
191         return whitelist;
192     }
193 }
194