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     };
237 
238     @Override
onCreate(Bundle savedInstanceState)239     public void onCreate(Bundle savedInstanceState) {
240         super.onCreate(savedInstanceState);
241         setContentView(R.layout.fs_main);
242         setPassFailButtonClickListeners();
243         setInfoResources(R.string.feature_summary, R.string.feature_summary_info, R.layout.fs_info);
244 
245         // some values used to detect warn-able conditions involving multiple
246         // features
247         boolean hasWifi = false;
248         boolean hasTelephony = false;
249         boolean hasBluetooth = false;
250         boolean hasIllegalFeature = false;
251 
252         // get list of all features device thinks it has, & store in a HashMap
253         // for fast lookups
254         HashMap<String, String> actualFeatures = new HashMap<String, String>();
255         for (FeatureInfo fi : getPackageManager().getSystemAvailableFeatures()) {
256             actualFeatures.put(fi.name, fi.name);
257         }
258 
259         // data structure that the SimpleAdapter will use to populate ListView
260         ArrayList<HashMap<String, Object>> listViewData = new ArrayList<HashMap<String, Object>>();
261 
262         // roll over all known features & check whether device reports them
263         boolean present = false;
264         int statusIcon;
265         Set<Feature> features = new LinkedHashSet<Feature>();
266 
267         // add features from latest to last so that the latest requirements are put in the set first
268         int apiVersion = Build.VERSION.SDK_INT;
269         if (apiVersion >= Build.VERSION_CODES.M) {
270             Collections.addAll(features, ALL_MNC_FEATURES);
271         }
272         if (apiVersion >= Build.VERSION_CODES.LOLLIPOP) {
273             Collections.addAll(features, ALL_LOLLIPOP_FEATURES);
274         }
275         if (apiVersion >= Build.VERSION_CODES.KITKAT_WATCH) {
276             Collections.addAll(features, ALL_KITKAT_WATCH_FEATURES);
277         }
278         if (apiVersion >= Build.VERSION_CODES.KITKAT) {
279             Collections.addAll(features, ALL_KITKAT_FEATURES);
280         }
281         if (apiVersion >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
282             Collections.addAll(features, ALL_JELLY_BEAN_MR2_FEATURES);
283         }
284         if (apiVersion >= Build.VERSION_CODES.JELLY_BEAN) {
285             Collections.addAll(features, ALL_JELLY_BEAN_FEATURES);
286         }
287         if (apiVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
288             Collections.addAll(features, ALL_ICE_CREAM_SANDWICH_FEATURES);
289         }
290         if (apiVersion >= Build.VERSION_CODES.HONEYCOMB_MR2) {
291             Collections.addAll(features, ALL_HONEYCOMB_MR2_FEATURES);
292         }
293         if (apiVersion >= Build.VERSION_CODES.HONEYCOMB_MR1) {
294             Collections.addAll(features, ALL_HONEYCOMB_MR1_FEATURES);
295         }
296         if (apiVersion >= Build.VERSION_CODES.HONEYCOMB) {
297             Collections.addAll(features, ALL_HONEYCOMB_FEATURES);
298         }
299         if (apiVersion >= Build.VERSION_CODES.GINGERBREAD_MR1) {
300             Collections.addAll(features, ALL_GINGERBREAD_MR1_FEATURES);
301         }
302         if (apiVersion >= Build.VERSION_CODES.GINGERBREAD) {
303             Collections.addAll(features, ALL_GINGERBREAD_FEATURES);
304         }
305         if (apiVersion >= Build.VERSION_CODES.FROYO) {
306             Collections.addAll(features, ALL_FROYO_FEATURES);
307         }
308         if (apiVersion >= Build.VERSION_CODES.ECLAIR_MR1) {
309             Collections.addAll(features, ALL_ECLAIR_FEATURES);
310         }
311         for (Feature f : features) {
312             HashMap<String, Object> row = new HashMap<String, Object>();
313             listViewData.add(row);
314             present = actualFeatures.containsKey(f.name);
315             if (present) {
316                 // device reports it -- yay! set the happy icon
317                 hasWifi = hasWifi || PackageManager.FEATURE_WIFI.equals(f.name);
318                 hasTelephony = hasTelephony || PackageManager.FEATURE_TELEPHONY.equals(f.name);
319                 hasBluetooth = hasBluetooth || PackageManager.FEATURE_BLUETOOTH.equals(f.name);
320                 statusIcon = R.drawable.fs_good;
321                 actualFeatures.remove(f.name);
322             } else if (!present && f.required) {
323                 // it's required, but device doesn't report it. Boo, set the
324                 // bogus icon
325                 statusIcon = R.drawable.fs_error;
326             } else {
327                 // device doesn't report it, but it's not req'd, so can't tell
328                 // if there's a problem
329                 statusIcon = R.drawable.fs_indeterminate;
330             }
331             row.put("feature", f.name);
332             row.put("icon", statusIcon);
333         }
334 
335         // now roll over any remaining features (which are non-standard)
336         for (String feature : actualFeatures.keySet()) {
337             if (feature == null || "".equals(feature))
338                 continue;
339             HashMap<String, Object> row = new HashMap<String, Object>();
340             listViewData.add(row);
341             row.put("feature", feature);
342             if (feature.startsWith("android")) { // intentionally not "android."
343                 // sorry, you're not allowed to squat in the official namespace;
344                 // set bogus icon
345                 row.put("icon", R.drawable.fs_error);
346                 hasIllegalFeature = true;
347             } else {
348                 // non-standard features are okay, but flag them just in case
349                 row.put("icon", R.drawable.fs_warning);
350             }
351         }
352 
353         // sort the ListView's data to group by icon type, for easier reading by
354         // humans
355         final HashMap<Integer, Integer> idMap = new HashMap<Integer, Integer>();
356         idMap.put(R.drawable.fs_error, 0);
357         idMap.put(R.drawable.fs_warning, 1);
358         idMap.put(R.drawable.fs_indeterminate, 2);
359         idMap.put(R.drawable.fs_good, 3);
360         Collections.sort(listViewData, new Comparator<HashMap<String, Object>>() {
361             public int compare(HashMap<String, Object> left, HashMap<String, Object> right) {
362                 int leftId = idMap.get(left.get("icon"));
363                 int rightId = idMap.get(right.get("icon"));
364                 if (leftId == rightId) {
365                     return ((String) left.get("feature")).compareTo((String) right.get("feature"));
366                 }
367                 if (leftId < rightId)
368                     return -1;
369                 return 1;
370             }
371         });
372 
373         // Set up the SimpleAdapter used to populate the ListView
374         SimpleAdapter adapter = new SimpleAdapter(this, listViewData, R.layout.fs_row,
375                 new String[] {
376                         "feature", "icon"
377                 }, new int[] {
378                         R.id.fs_feature, R.id.fs_icon
379                 });
380         adapter.setViewBinder(new SimpleAdapter.ViewBinder() {
381             public boolean setViewValue(View view, Object data, String repr) {
382                 try {
383                     if (view instanceof ImageView) {
384                         ((ImageView) view).setImageResource((Integer) data);
385                     } else if (view instanceof TextView) {
386                         ((TextView) view).setText((String) data);
387                     } else {
388                         return false;
389                     }
390                     return true;
391                 } catch (ClassCastException e) {
392                     return false;
393                 }
394             }
395         });
396         setListAdapter(adapter);
397 
398         // finally, check for our second-order error cases and set warning text
399         // if necessary
400         StringBuffer sb = new StringBuffer();
401         if (hasIllegalFeature) {
402             sb.append(getResources().getString(R.string.fs_disallowed)).append("\n");
403         }
404 
405         if (!hasWifi && !hasTelephony && !hasBluetooth) {
406             sb.append(getResources().getString(R.string.fs_missing_wifi_telephony)).append("\n");
407         }
408 
409         String warnings = sb.toString().trim();
410         if (warnings == null || "".equals(warnings)) {
411             ((TextView) (findViewById(R.id.fs_warnings))).setVisibility(View.GONE);
412         } else {
413             ((TextView) (findViewById(R.id.fs_warnings))).setText(warnings);
414         }
415     }
416 }
417