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.annotation.TargetApi;
19 import android.app.admin.DevicePolicyManager;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.pm.ApplicationInfo;
23 import android.content.pm.PackageInfo;
24 import android.content.pm.PackageManager;
25 import android.content.pm.PermissionInfo;
26 import android.os.Build;
27 import android.os.Process;
28 import com.android.compatibility.common.util.DeviceInfoStore;
29 import com.android.compatibility.common.util.PackageUtil;
30 
31 import java.io.IOException;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.HashSet;
35 import java.util.List;
36 import java.util.Set;
37 
38 /**
39  * PackageDeviceInfo collector.
40  */
41 @TargetApi(Build.VERSION_CODES.N)
42 public class PackageDeviceInfo extends DeviceInfo {
43 
44     private static final String PLATFORM = "android";
45     private static final String PLATFORM_PERMISSION_PREFIX = "android.";
46 
47     private static final String PACKAGE = "package";
48     private static final String NAME = "name";
49     private static final String VERSION_NAME = "version_name";
50     private static final String SYSTEM_PRIV = "system_priv";
51     private static final String PRIV_APP_DIR = "/system/priv-app";
52     private static final String MIN_SDK = "min_sdk";
53     private static final String TARGET_SDK = "target_sdk";
54 
55     private static final String REQUESTED_PERMISSIONS = "requested_permissions";
56     private static final String PERMISSION_NAME = "name";
57     private static final String PERMISSION_FLAGS = "flags";
58     private static final String PERMISSION_GROUP = "permission_group";
59     private static final String PERMISSION_PROTECTION = "protection_level";
60     private static final String PERMISSION_PROTECTION_FLAGS = "protection_level_flags";
61 
62     private static final String PERMISSION_TYPE = "type";
63     private static final int PERMISSION_TYPE_SYSTEM = 1;
64     private static final int PERMISSION_TYPE_OEM = 2;
65     private static final int PERMISSION_TYPE_CUSTOM = 3;
66 
67     private static final String HAS_SYSTEM_UID = "has_system_uid";
68 
69     private static final String SHARES_INSTALL_PERMISSION = "shares_install_packages_permission";
70     private static final String INSTALL_PACKAGES_PERMISSION = "android.permission.INSTALL_PACKAGES";
71 
72     private static final String SHA256_CERT = "sha256_cert";
73 
74     private static final String CONFIG_NOTIFICATION_ACCESS = "config_defaultListenerAccessPackages";
75     private static final String HAS_DEFAULT_NOTIFICATION_ACCESS = "has_default_notification_access";
76 
77     private static final String UID = "uid";
78     private static final String IS_ACTIVE_ADMIN = "is_active_admin";
79 
80     private static final String CONFIG_ACCESSIBILITY_SERVICE = "config_defaultAccessibilityService";
81     private static final String DEFAULT_ACCESSIBILITY_SERVICE = "is_default_accessibility_service";
82 
83 
84     @Override
collectDeviceInfo(DeviceInfoStore store)85     protected void collectDeviceInfo(DeviceInfoStore store) throws Exception {
86         final PackageManager pm = getContext().getPackageManager();
87 
88         final List<PackageInfo> allPackages =
89                 pm.getInstalledPackages(PackageManager.GET_PERMISSIONS);
90         final Set<String> defaultNotificationListeners =
91                 getColonSeparatedPackageList(CONFIG_NOTIFICATION_ACCESS);
92 
93         final Set<String> deviceAdminPackages = getActiveDeviceAdminPackages();
94 
95         final ComponentName defaultAccessibilityComponent = getDefaultAccessibilityComponent();
96 
97         // Platform permission data used to tag permissions information with sourcing information
98         final PackageInfo platformInfo = pm.getPackageInfo(PLATFORM , PackageManager.GET_PERMISSIONS);
99         final Set<String> platformPermissions = new HashSet<String>();
100         for (PermissionInfo permission : platformInfo.permissions) {
101           platformPermissions.add(permission.name);
102         }
103 
104         store.startArray(PACKAGE);
105         for (PackageInfo pkg : allPackages) {
106             store.startGroup();
107             store.addResult(NAME, pkg.packageName);
108             store.addResult(VERSION_NAME, pkg.versionName);
109 
110             collectPermissions(store, pm, platformPermissions, pkg);
111             collectionApplicationInfo(store, pm, pkg);
112 
113             store.addResult(HAS_DEFAULT_NOTIFICATION_ACCESS,
114                     defaultNotificationListeners.contains(pkg.packageName));
115 
116             store.addResult(IS_ACTIVE_ADMIN, deviceAdminPackages.contains(pkg.packageName));
117 
118             boolean isDefaultAccessibilityComponent = false;
119             if (defaultAccessibilityComponent != null) {
120               isDefaultAccessibilityComponent = pkg.packageName.equals(
121                       defaultAccessibilityComponent.getPackageName()
122               );
123             }
124             store.addResult(DEFAULT_ACCESSIBILITY_SERVICE, isDefaultAccessibilityComponent);
125 
126             String sha256_cert = PackageUtil.computePackageSignatureDigest(pkg.packageName);
127             store.addResult(SHA256_CERT, sha256_cert);
128 
129             store.endGroup();
130         }
131         store.endArray(); // "package"
132     }
133 
collectPermissions(DeviceInfoStore store, PackageManager pm, Set<String> systemPermissions, PackageInfo pkg)134     private static void collectPermissions(DeviceInfoStore store,
135                                            PackageManager pm,
136                                            Set<String> systemPermissions,
137                                            PackageInfo pkg) throws IOException
138     {
139         store.startArray(REQUESTED_PERMISSIONS);
140         if (pkg.requestedPermissions != null && pkg.requestedPermissions.length > 0) {
141             for (String permission : pkg.requestedPermissions) {
142                 if (permission == null) continue;
143 
144                 try {
145                     final PermissionInfo pi = pm.getPermissionInfo(permission, 0);
146 
147                     store.startGroup();
148                     store.addResult(PERMISSION_NAME, permission);
149                     writePermissionsDetails(pi, store);
150 
151                     final boolean isPlatformPermission = systemPermissions.contains(permission);
152                     if (isPlatformPermission) {
153                       final boolean isAndroidPermission = permission.startsWith(PLATFORM_PERMISSION_PREFIX);
154                       if (isAndroidPermission) {
155                         store.addResult(PERMISSION_TYPE, PERMISSION_TYPE_SYSTEM);
156                       } else {
157                         store.addResult(PERMISSION_TYPE, PERMISSION_TYPE_OEM);
158                       }
159                     } else {
160                       store.addResult(PERMISSION_TYPE, PERMISSION_TYPE_CUSTOM);
161                     }
162 
163                     store.endGroup();
164                 } catch (PackageManager.NameNotFoundException e) {
165                     // ignore unrecognized permission and continue
166                 }
167             }
168         }
169         store.endArray();
170     }
171 
collectionApplicationInfo(DeviceInfoStore store, PackageManager pm, PackageInfo pkg)172     private static void collectionApplicationInfo(DeviceInfoStore store,
173                                                   PackageManager pm,
174                                                   PackageInfo pkg) throws IOException {
175         final ApplicationInfo appInfo = pkg.applicationInfo;
176         if (appInfo != null) {
177             String dir = appInfo.sourceDir;
178             store.addResult(SYSTEM_PRIV, dir != null && dir.startsWith(PRIV_APP_DIR));
179 
180             store.addResult(MIN_SDK, appInfo.minSdkVersion);
181             store.addResult(TARGET_SDK, appInfo.targetSdkVersion);
182 
183             store.addResult(HAS_SYSTEM_UID, appInfo.uid < Process.FIRST_APPLICATION_UID);
184 
185             final boolean canInstall = sharesUidWithInstallerPackage(pm, appInfo.uid);
186             store.addResult(SHARES_INSTALL_PERMISSION, canInstall);
187 
188             store.addResult(UID, appInfo.uid);
189         }
190     }
191 
sharesUidWithInstallerPackage(PackageManager pm, int uid)192     private static boolean sharesUidWithInstallerPackage(PackageManager pm, int uid) {
193         final String[] sharesUidWith = pm.getPackagesForUid(uid);
194 
195         if (sharesUidWith == null) {
196             return false;
197         }
198 
199         // Approx 20 permissions per package for rough estimate of sizing
200         final int capacity = sharesUidWith.length * 20;
201         final List<String> sharedPermissions = new ArrayList<>(capacity);
202         for (String pkg :sharesUidWith){
203             try {
204                 final PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_PERMISSIONS);
205 
206                 if (info.requestedPermissions == null) {
207                     continue;
208                 }
209 
210                 for (String p : info.requestedPermissions) {
211                     if (p != null) {
212                         sharedPermissions.add(p);
213                     }
214                 }
215             } catch (PackageManager.NameNotFoundException e) {
216                 // ignore, continue
217             }
218         }
219 
220         return sharedPermissions.contains(PackageDeviceInfo.INSTALL_PACKAGES_PERMISSION);
221     }
222 
writePermissionsDetails(PermissionInfo pi, DeviceInfoStore store)223     private static void writePermissionsDetails(PermissionInfo pi, DeviceInfoStore store)
224             throws IOException {
225         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
226             store.addResult(PERMISSION_FLAGS, pi.flags);
227         } else {
228             store.addResult(PERMISSION_FLAGS, 0);
229         }
230 
231         store.addResult(PERMISSION_GROUP, pi.group);
232 
233         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
234             store.addResult(PERMISSION_PROTECTION, pi.getProtection());
235             store.addResult(PERMISSION_PROTECTION_FLAGS, pi.getProtectionFlags());
236         } else {
237             store.addResult(PERMISSION_PROTECTION,
238                     pi.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE);
239             store.addResult(PERMISSION_PROTECTION_FLAGS,
240                     pi.protectionLevel & ~PermissionInfo.PROTECTION_MASK_BASE);
241         }
242     }
243 
getActiveDeviceAdminPackages()244     private Set<String> getActiveDeviceAdminPackages() {
245         final DevicePolicyManager dpm = (DevicePolicyManager)
246                 getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
247 
248         final List<ComponentName> components = dpm.getActiveAdmins();
249         if (components == null) {
250             return new HashSet<>(0);
251         }
252 
253         final HashSet<String> packages = new HashSet<>(components.size());
254         for (ComponentName component : components) {
255             packages.add(component.getPackageName());
256         }
257 
258         return packages;
259     }
260 
getDefaultAccessibilityComponent()261     private ComponentName getDefaultAccessibilityComponent() {
262         final String defaultAccessibilityServiceComponent =
263                 getRawDeviceConfig(CONFIG_ACCESSIBILITY_SERVICE);
264         return ComponentName.unflattenFromString(defaultAccessibilityServiceComponent);
265     }
266 
267     /**
268      * Parses and returns a set of package ids from a configuration value
269      * e.g config_defaultListenerAccessPackages
270      **/
getColonSeparatedPackageList(String name)271     private Set<String> getColonSeparatedPackageList(String name) {
272         String raw = getRawDeviceConfig(name);
273         String[] packages = raw.split(":");
274         return new HashSet<>(Arrays.asList(packages));
275     }
276 
277     /** Returns the value of a device configuration setting available in android.internal.R.* **/
getRawDeviceConfig(String name)278     private String getRawDeviceConfig(String name) {
279         return getContext()
280                 .getResources()
281                 .getString(getDeviceResourcesIdentifier(name, "string"));
282     }
283 
getDeviceResourcesIdentifier(String name, String type)284     private int getDeviceResourcesIdentifier(String name, String type) {
285         return getContext()
286                 .getResources()
287                 .getIdentifier(name, type, "android");
288     }
289 }
290 
291