1 /*
2  * Copyright (C) 2010 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 /*
18  * This file references fs_error.png, fs_good.png, fs_indeterminate.png,
19  * and fs_warning.png which are licensed under Creative Commons 3.0
20  * by fatcow.com.
21  * http://www.fatcow.com/free-icons/
22  * http://creativecommons.org/licenses/by/3.0/us/
23  */
24 
25 package com.android.cts.verifier.features;
26 
27 import com.android.cts.verifier.PassFailButtons;
28 import com.android.cts.verifier.R;
29 
30 import android.content.pm.FeatureInfo;
31 import android.content.pm.PackageManager;
32 import android.os.Build;
33 import android.os.Bundle;
34 import android.view.View;
35 import android.widget.ImageView;
36 import android.widget.SimpleAdapter;
37 import android.widget.TextView;
38 
39 import java.util.ArrayList;
40 import java.util.Collections;
41 import java.util.Comparator;
42 import java.util.HashMap;
43 import java.util.LinkedHashSet;
44 import java.util.Set;
45 
46 public class FeatureSummaryActivity extends PassFailButtons.ListActivity {
47     /**
48      * Simple storage class for data about an Android feature.
49      */
50     static class Feature {
51         /**
52          * The name of the feature. Should be one of the PackageManager.FEATURE*
53          * constants.
54          */
55         public String name;
56 
57         /**
58          * Indicates whether the field is present on the current device.
59          */
60         public boolean present;
61 
62         /**
63          * Indicates whether the field is required for the current device.
64          */
65         public boolean required;
66 
67         /**
68          * Constructor does not include 'present' because that's a detected
69          * value, and not set during creation.
70          *
71          * @param name value for this.name
72          * @param required value for this.required
73          */
Feature(String name, boolean required)74         public Feature(String name, boolean required) {
75             this.name = name;
76             this.required = required;
77             this.present = false;
78         }
79 
80         @Override
equals(Object o)81         public boolean equals(Object o) {
82             if (this == o) {
83                 return true;
84             } else if (o == null || !(o instanceof Feature)) {
85                 return false;
86             } else {
87                 Feature feature = (Feature) o;
88                 return name.equals(feature.name);
89             }
90         }
91 
92         @Override
hashCode()93         public int hashCode() {
94             return name.hashCode();
95         }
96     }
97 
98     public static final Feature[] ALL_ECLAIR_FEATURES = {
99             new Feature(PackageManager.FEATURE_CAMERA, true),
100             new Feature(PackageManager.FEATURE_CAMERA_AUTOFOCUS, false),
101             new Feature(PackageManager.FEATURE_CAMERA_FLASH, false),
102             new Feature(PackageManager.FEATURE_LIVE_WALLPAPER, false),
103             new Feature(PackageManager.FEATURE_SENSOR_LIGHT, false),
104             new Feature(PackageManager.FEATURE_SENSOR_PROXIMITY, false),
105             new Feature(PackageManager.FEATURE_TELEPHONY, false),
106             new Feature(PackageManager.FEATURE_TELEPHONY_CDMA, false),
107             new Feature(PackageManager.FEATURE_TELEPHONY_GSM, false),
108     };
109 
110     public static final Feature[] ALL_FROYO_FEATURES = {
111             new Feature("android.hardware.bluetooth", true),
112             new Feature("android.hardware.location", true),
113             new Feature("android.hardware.location.gps", true),
114             new Feature("android.hardware.location.network", true),
115             new Feature("android.hardware.microphone", true),
116             new Feature("android.hardware.sensor.accelerometer", true),
117             new Feature("android.hardware.sensor.compass", true),
118             new Feature("android.hardware.touchscreen", true),
119             new Feature("android.hardware.touchscreen.multitouch", false),
120             new Feature("android.hardware.touchscreen.multitouch.distinct", false),
121             new Feature("android.hardware.wifi", false),
122     };
123 
124     public static final Feature[] ALL_GINGERBREAD_FEATURES = {
125             // Required features in prior releases that became optional in GB
126             new Feature("android.hardware.bluetooth", false),
127             new Feature("android.hardware.camera", false),
128             new Feature("android.hardware.location.gps", false),
129             new Feature("android.hardware.microphone", false),
130             new Feature("android.hardware.sensor.accelerometer", false),
131             new Feature("android.hardware.sensor.compass", false),
132 
133             // New features in GB
134             new Feature("android.hardware.audio.low_latency", false),
135             new Feature("android.hardware.camera.front", false),
136             new Feature("android.hardware.nfc", false),
137             new Feature("android.hardware.sensor.barometer", false),
138             new Feature("android.hardware.sensor.gyroscope", false),
139             new Feature("android.hardware.touchscreen.multitouch.jazzhand", false),
140             new Feature("android.software.sip", false),
141             new Feature("android.software.sip.voip", false),
142     };
143 
144     public static final Feature[] ALL_GINGERBREAD_MR1_FEATURES = {
145             new Feature("android.hardware.usb.accessory", false),
146     };
147 
148     public static final Feature[] ALL_HONEYCOMB_FEATURES = {
149             // Required features in prior releases that became optional in HC
150             new Feature("android.hardware.touchscreen", false),
151 
152             new Feature("android.hardware.faketouch", true),
153     };
154 
155     public static final Feature[] ALL_HONEYCOMB_MR1_FEATURES = {
156             new Feature("android.hardware.usb.host", false),
157             new Feature("android.hardware.usb.accessory", false),
158     };
159 
160     public static final Feature[] ALL_HONEYCOMB_MR2_FEATURES = {
161             new Feature("android.hardware.faketouch.multitouch.distinct", false),
162             new Feature("android.hardware.faketouch.multitouch.jazzhand", false),
163             new Feature("android.hardware.screen.landscape", false),
164             new Feature("android.hardware.screen.portrait", false),
165     };
166 
167     public static final Feature[] ALL_ICE_CREAM_SANDWICH_FEATURES = {
168             new Feature(PackageManager.FEATURE_WIFI_DIRECT, false),
169     };
170 
171     public static final Feature[] ALL_JELLY_BEAN_FEATURES = {
172             // Required features in prior releases that became optional
173             new Feature(PackageManager.FEATURE_FAKETOUCH, false),
174 
175             //new feature in JB
176             new Feature(PackageManager.FEATURE_TELEVISION, false),
177     };
178 
179     public static final Feature[] ALL_JELLY_BEAN_MR2_FEATURES = {
180             new Feature("android.software.app_widgets", false),
181             new Feature("android.software.input_methods", false),
182             new Feature("android.software.home_screen", false),
183             new Feature("android.hardware.bluetooth_le", false),
184             new Feature("android.hardware.camera.any", false),
185     };
186 
187     public static final Feature[] ALL_KITKAT_FEATURES = {
188             new Feature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION, false),
189             new Feature(PackageManager.FEATURE_CONSUMER_IR, false),
190             new Feature(PackageManager.FEATURE_DEVICE_ADMIN, false),
191             new Feature(PackageManager.FEATURE_SENSOR_STEP_COUNTER, false),
192             new Feature(PackageManager.FEATURE_SENSOR_STEP_DETECTOR, false),
193     };
194 
195     public static final Feature[] ALL_KITKAT_WATCH_FEATURES = {
196             new Feature(PackageManager.FEATURE_SENSOR_HEART_RATE, false),
197             new Feature(PackageManager.FEATURE_BACKUP, false),
198             new Feature(PackageManager.FEATURE_PRINTING, false),
199             new Feature(PackageManager.FEATURE_WATCH, false),
200             new Feature(PackageManager.FEATURE_WEBVIEW, false),
201             new Feature(PackageManager.FEATURE_CAMERA_EXTERNAL, false),
202     };
203 
204     public static final Feature[] ALL_LOLLIPOP_FEATURES = {
205             // New features in L
206             new Feature(PackageManager.FEATURE_AUDIO_OUTPUT, false),
207             new Feature(PackageManager.FEATURE_CAMERA_CAPABILITY_MANUAL_POST_PROCESSING, false),
208             new Feature(PackageManager.FEATURE_CAMERA_CAPABILITY_MANUAL_SENSOR, false),
209             new Feature(PackageManager.FEATURE_CAMERA_CAPABILITY_RAW, false),
210             new Feature(PackageManager.FEATURE_CAMERA_LEVEL_FULL, false),
211             new Feature(PackageManager.FEATURE_CONNECTION_SERVICE, false),
212             new Feature(PackageManager.FEATURE_GAMEPAD, false),
213             new Feature(PackageManager.FEATURE_LEANBACK, false),
214             new Feature(PackageManager.FEATURE_LIVE_TV, false),
215             new Feature(PackageManager.FEATURE_MANAGED_USERS, false),
216             new Feature(PackageManager.FEATURE_OPENGLES_EXTENSION_PACK, false),
217             new Feature(PackageManager.FEATURE_SECURELY_REMOVES_USERS, false),
218             new Feature(PackageManager.FEATURE_SENSOR_AMBIENT_TEMPERATURE, false),
219             new Feature(PackageManager.FEATURE_SENSOR_HEART_RATE_ECG, false),
220             new Feature(PackageManager.FEATURE_SENSOR_RELATIVE_HUMIDITY, false),
221             new Feature(PackageManager.FEATURE_VERIFIED_BOOT, false),
222 
223             // Features explicitly made optional in L
224             new Feature(PackageManager.FEATURE_LOCATION_NETWORK, false),
225 
226             // New hidden features in L
227             new Feature("android.hardware.ethernet", false),
228             new Feature("android.hardware.hdmi.cec", false),
229             new Feature("android.software.leanback_only", false),
230             new Feature("android.software.voice_recognizers", false),
231     };
232 
233     public static final Feature[] ALL_MNC_FEATURES = {
234             new Feature(PackageManager.FEATURE_MIDI, false),
235             new Feature(PackageManager.FEATURE_AUDIO_PRO, false),
236             new Feature(PackageManager.FEATURE_AUTOMOTIVE, false),
237             new Feature(PackageManager.FEATURE_HIFI_SENSORS, false),
238             new Feature(PackageManager.FEATURE_FINGERPRINT, false),
239     };
240 
241     public static final Feature[] ALL_NYC_FEATURES = {
242             new Feature(PackageManager.FEATURE_VR_MODE, false),
243             new Feature(PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE, false),
244             new Feature(PackageManager.FEATURE_VULKAN_HARDWARE_VERSION, false),
245             new Feature(PackageManager.FEATURE_VULKAN_HARDWARE_LEVEL, false),
246             new Feature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF, false),
247             new Feature(PackageManager.FEATURE_PICTURE_IN_PICTURE, false),
248             new Feature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT, false),
249             // FEATURE_FILE_BASED_ENCRYPTION is hide
250             new Feature("android.software.file_based_encryption", false),
251     };
252 
253     public static final Feature[] ALL_O_FEATURES = {
254             new Feature(PackageManager.FEATURE_VULKAN_HARDWARE_COMPUTE, false),
255             // FEATURE_TELEPHONY_CARRIERLOCK is SystemApi
256             new Feature("android.hardware.telephony.carrierlock", false),
257             new Feature(PackageManager.FEATURE_WIFI_AWARE, false),
258             new Feature(PackageManager.FEATURE_EMBEDDED, false),
259             new Feature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP, false),
260             new Feature(PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS, false),
261             new Feature(PackageManager.FEATURE_VR_HEADTRACKING, false),
262             // FEATURE_CTS is hide
263             new Feature("android.software.cts", false),
264             new Feature(PackageManager.FEATURE_WIFI_AWARE, false),
265     };
266 
267     @Override
onCreate(Bundle savedInstanceState)268     public void onCreate(Bundle savedInstanceState) {
269         super.onCreate(savedInstanceState);
270         setContentView(R.layout.fs_main);
271         setPassFailButtonClickListeners();
272         setInfoResources(R.string.feature_summary, R.string.feature_summary_info, R.layout.fs_info);
273 
274         // some values used to detect warn-able conditions involving multiple
275         // features
276         boolean hasWifi = false;
277         boolean hasTelephony = false;
278         boolean hasBluetooth = false;
279         boolean hasIllegalFeature = false;
280 
281         // get list of all features device thinks it has, & store in a HashMap
282         // for fast lookups
283         HashMap<String, String> actualFeatures = new HashMap<String, String>();
284         for (FeatureInfo fi : getPackageManager().getSystemAvailableFeatures()) {
285             actualFeatures.put(fi.name, fi.name);
286         }
287 
288         // data structure that the SimpleAdapter will use to populate ListView
289         ArrayList<HashMap<String, Object>> listViewData = new ArrayList<HashMap<String, Object>>();
290 
291         // roll over all known features & check whether device reports them
292         boolean present = false;
293         int statusIcon;
294         Set<Feature> features = new LinkedHashSet<Feature>();
295 
296         // add features from latest to last so that the latest requirements are put in the set first
297         int apiVersion = Build.VERSION.SDK_INT;
298         if (apiVersion >= Build.VERSION_CODES.O) {
299             Collections.addAll(features, ALL_O_FEATURES);
300         }
301         if (apiVersion >= Build.VERSION_CODES.N) {
302             Collections.addAll(features, ALL_NYC_FEATURES);
303         }
304         if (apiVersion >= Build.VERSION_CODES.M) {
305             Collections.addAll(features, ALL_MNC_FEATURES);
306         }
307         if (apiVersion >= Build.VERSION_CODES.LOLLIPOP) {
308             Collections.addAll(features, ALL_LOLLIPOP_FEATURES);
309         }
310         if (apiVersion >= Build.VERSION_CODES.KITKAT_WATCH) {
311             Collections.addAll(features, ALL_KITKAT_WATCH_FEATURES);
312         }
313         if (apiVersion >= Build.VERSION_CODES.KITKAT) {
314             Collections.addAll(features, ALL_KITKAT_FEATURES);
315         }
316         if (apiVersion >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
317             Collections.addAll(features, ALL_JELLY_BEAN_MR2_FEATURES);
318         }
319         if (apiVersion >= Build.VERSION_CODES.JELLY_BEAN) {
320             Collections.addAll(features, ALL_JELLY_BEAN_FEATURES);
321         }
322         if (apiVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
323             Collections.addAll(features, ALL_ICE_CREAM_SANDWICH_FEATURES);
324         }
325         if (apiVersion >= Build.VERSION_CODES.HONEYCOMB_MR2) {
326             Collections.addAll(features, ALL_HONEYCOMB_MR2_FEATURES);
327         }
328         if (apiVersion >= Build.VERSION_CODES.HONEYCOMB_MR1) {
329             Collections.addAll(features, ALL_HONEYCOMB_MR1_FEATURES);
330         }
331         if (apiVersion >= Build.VERSION_CODES.HONEYCOMB) {
332             Collections.addAll(features, ALL_HONEYCOMB_FEATURES);
333         }
334         if (apiVersion >= Build.VERSION_CODES.GINGERBREAD_MR1) {
335             Collections.addAll(features, ALL_GINGERBREAD_MR1_FEATURES);
336         }
337         if (apiVersion >= Build.VERSION_CODES.GINGERBREAD) {
338             Collections.addAll(features, ALL_GINGERBREAD_FEATURES);
339         }
340         if (apiVersion >= Build.VERSION_CODES.FROYO) {
341             Collections.addAll(features, ALL_FROYO_FEATURES);
342         }
343         if (apiVersion >= Build.VERSION_CODES.ECLAIR_MR1) {
344             Collections.addAll(features, ALL_ECLAIR_FEATURES);
345         }
346         for (Feature f : features) {
347             HashMap<String, Object> row = new HashMap<String, Object>();
348             listViewData.add(row);
349             present = actualFeatures.containsKey(f.name);
350             if (present) {
351                 // device reports it -- yay! set the happy icon
352                 hasWifi = hasWifi || PackageManager.FEATURE_WIFI.equals(f.name);
353                 hasTelephony = hasTelephony || PackageManager.FEATURE_TELEPHONY.equals(f.name);
354                 hasBluetooth = hasBluetooth || PackageManager.FEATURE_BLUETOOTH.equals(f.name);
355                 statusIcon = R.drawable.fs_good;
356                 actualFeatures.remove(f.name);
357             } else if (!present && f.required) {
358                 // it's required, but device doesn't report it. Boo, set the
359                 // bogus icon
360                 statusIcon = R.drawable.fs_error;
361             } else {
362                 // device doesn't report it, but it's not req'd, so can't tell
363                 // if there's a problem
364                 statusIcon = R.drawable.fs_indeterminate;
365             }
366             row.put("feature", f.name);
367             row.put("icon", statusIcon);
368         }
369 
370         // now roll over any remaining features (which are non-standard)
371         for (String feature : actualFeatures.keySet()) {
372             if (feature == null || "".equals(feature))
373                 continue;
374             HashMap<String, Object> row = new HashMap<String, Object>();
375             listViewData.add(row);
376             row.put("feature", feature);
377             if (feature.startsWith("android")) { // intentionally not "android."
378                 // sorry, you're not allowed to squat in the official namespace;
379                 // set bogus icon
380                 row.put("icon", R.drawable.fs_error);
381                 hasIllegalFeature = true;
382             } else {
383                 // non-standard features are okay, but flag them just in case
384                 row.put("icon", R.drawable.fs_warning);
385             }
386         }
387 
388         // sort the ListView's data to group by icon type, for easier reading by
389         // humans
390         final HashMap<Integer, Integer> idMap = new HashMap<Integer, Integer>();
391         idMap.put(R.drawable.fs_error, 0);
392         idMap.put(R.drawable.fs_warning, 1);
393         idMap.put(R.drawable.fs_indeterminate, 2);
394         idMap.put(R.drawable.fs_good, 3);
395         Collections.sort(listViewData, new Comparator<HashMap<String, Object>>() {
396             public int compare(HashMap<String, Object> left, HashMap<String, Object> right) {
397                 int leftId = idMap.get(left.get("icon"));
398                 int rightId = idMap.get(right.get("icon"));
399                 if (leftId == rightId) {
400                     return ((String) left.get("feature")).compareTo((String) right.get("feature"));
401                 }
402                 if (leftId < rightId)
403                     return -1;
404                 return 1;
405             }
406         });
407 
408         // Set up the SimpleAdapter used to populate the ListView
409         SimpleAdapter adapter = new SimpleAdapter(this, listViewData, R.layout.fs_row,
410                 new String[] {
411                         "feature", "icon"
412                 }, new int[] {
413                         R.id.fs_feature, R.id.fs_icon
414                 });
415         adapter.setViewBinder(new SimpleAdapter.ViewBinder() {
416             public boolean setViewValue(View view, Object data, String repr) {
417                 try {
418                     if (view instanceof ImageView) {
419                         ((ImageView) view).setImageResource((Integer) data);
420                     } else if (view instanceof TextView) {
421                         ((TextView) view).setText((String) data);
422                     } else {
423                         return false;
424                     }
425                     return true;
426                 } catch (ClassCastException e) {
427                     return false;
428                 }
429             }
430         });
431         setListAdapter(adapter);
432 
433         // finally, check for our second-order error cases and set warning text
434         // if necessary
435         StringBuffer sb = new StringBuffer();
436         if (hasIllegalFeature) {
437             sb.append(getResources().getString(R.string.fs_disallowed)).append("\n");
438         }
439 
440         if (!hasWifi && !hasTelephony && !hasBluetooth) {
441             sb.append(getResources().getString(R.string.fs_missing_wifi_telephony)).append("\n");
442         }
443 
444         String warnings = sb.toString().trim();
445         if (warnings == null || "".equals(warnings)) {
446             ((TextView) (findViewById(R.id.fs_warnings))).setVisibility(View.GONE);
447         } else {
448             ((TextView) (findViewById(R.id.fs_warnings))).setText(warnings);
449         }
450     }
451 }
452