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