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