Home | History | Annotate | Download | only in intent
      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 static android.signature.cts.CurrentApi.CURRENT_API_FILE;
     19 import static android.signature.cts.CurrentApi.SYSTEM_CURRENT_API_FILE;
     20 import static android.signature.cts.CurrentApi.SYSTEM_REMOVED_API_FILE;
     21 
     22 import android.content.pm.ApplicationInfo;
     23 import android.content.pm.PackageManager;
     24 import android.signature.cts.ApiDocumentParser;
     25 import android.signature.cts.JDiffClassDescription;
     26 import android.signature.cts.JDiffClassDescription.JDiffField;
     27 import android.support.test.InstrumentationRegistry;
     28 import android.support.test.runner.AndroidJUnit4;
     29 import android.util.Log;
     30 
     31 import com.android.compatibility.common.util.DynamicConfigDeviceSide;
     32 
     33 import org.junit.Assert;
     34 import org.junit.Before;
     35 import org.junit.Test;
     36 import org.junit.runner.RunWith;
     37 import org.xmlpull.v1.XmlPullParserException;
     38 
     39 import java.io.BufferedReader;
     40 import java.io.File;
     41 import java.io.FileInputStream;
     42 import java.io.IOException;
     43 import java.io.InputStreamReader;
     44 import java.util.HashSet;
     45 import java.util.List;
     46 import java.util.Set;
     47 
     48 /**
     49  * Validate that the android intents used by APKs on this device are part of the
     50  * platform.
     51  */
     52 @RunWith(AndroidJUnit4.class)
     53 public class IntentTest {
     54     private static final String TAG = IntentTest.class.getSimpleName();
     55 
     56     private static final File SIGNATURE_TEST_PACKGES =
     57             new File("/data/local/tmp/signature-test-packages");
     58     private static final String ANDROID_INTENT_PREFIX = "android.intent.action";
     59     private static final String ACTION_LINE_PREFIX = "          Action: ";
     60     private static final String MODULE_NAME = "CtsIntentSignatureTestCases";
     61 
     62     private PackageManager mPackageManager;
     63     private Set<String> intentWhitelist;
     64 
     65     @Before
     66     public void setupPackageManager() throws Exception {
     67       mPackageManager = InstrumentationRegistry.getContext().getPackageManager();
     68       intentWhitelist = getIntentWhitelist();
     69     }
     70 
     71     @Test
     72     public void shouldNotFindUnexpectedIntents() throws Exception {
     73         Set<String> platformIntents = lookupPlatformIntents();
     74         platformIntents.addAll(intentWhitelist);
     75 
     76         Set<String> allInvalidIntents = new HashSet<>();
     77 
     78         Set<String> errors = new HashSet<>();
     79         List<ApplicationInfo> packages =
     80             mPackageManager.getInstalledApplications(PackageManager.GET_META_DATA);
     81         for (ApplicationInfo appInfo : packages) {
     82             if (!isSystemApp(appInfo) && !isUpdatedSystemApp(appInfo)) {
     83                 // Only examine system apps
     84                 continue;
     85             }
     86             Set<String> invalidIntents = new HashSet<>();
     87             Set<String> activeIntents = lookupActiveIntents(appInfo.packageName);
     88 
     89             for (String activeIntent : activeIntents) {
     90               String intent = activeIntent.trim();
     91               if (!platformIntents.contains(intent) &&
     92                     intent.startsWith(ANDROID_INTENT_PREFIX)) {
     93                   invalidIntents.add(activeIntent);
     94                   allInvalidIntents.add(activeIntent);
     95               }
     96             }
     97 
     98             String error = String.format("Package: %s Invalid Intent: %s",
     99                   appInfo.packageName, invalidIntents);
    100             if (!invalidIntents.isEmpty()) {
    101                 errors.add(error);
    102             }
    103         }
    104 
    105         // Log the whitelist line to make it easy to update.
    106         for (String intent : allInvalidIntents) {
    107            Log.d(TAG, String.format("whitelist.add(\"%s\");", intent));
    108         }
    109 
    110         Assert.assertTrue(errors.toString(), errors.isEmpty());
    111     }
    112 
    113     private Set<String> lookupPlatformIntents() {
    114         try {
    115             Set<String> intents = new HashSet<>();
    116             intents.addAll(parse(CURRENT_API_FILE));
    117             intents.addAll(parse(SYSTEM_CURRENT_API_FILE));
    118             intents.addAll(parse(SYSTEM_REMOVED_API_FILE));
    119             return intents;
    120         } catch (XmlPullParserException | IOException e) {
    121             throw new RuntimeException("failed to parse", e);
    122         }
    123     }
    124 
    125     private static Set<String> parse(String apiFileName)
    126             throws XmlPullParserException, IOException {
    127 
    128         Set<String> androidIntents = new HashSet<>();
    129 
    130         ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG);
    131 
    132         apiDocumentParser.parseAsStream(new FileInputStream(new File(apiFileName))).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 
    148     private static boolean isSystemApp(ApplicationInfo applicationInfo) {
    149         return (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
    150     }
    151 
    152     private static boolean isUpdatedSystemApp(ApplicationInfo applicationInfo) {
    153         return (applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
    154     }
    155 
    156     private static Set<String> lookupActiveIntents(String packageName) {
    157         HashSet<String> activeIntents = new HashSet<>();
    158         File dumpsysPackage = new File(SIGNATURE_TEST_PACKGES, 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 
    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