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 boolean hasTelevision = false; 281 282 // get list of all features device thinks it has, & store in a HashMap 283 // for fast lookups 284 HashMap<String, String> actualFeatures = new HashMap<String, String>(); 285 for (FeatureInfo fi : getPackageManager().getSystemAvailableFeatures()) { 286 actualFeatures.put(fi.name, fi.name); 287 } 288 289 // data structure that the SimpleAdapter will use to populate ListView 290 ArrayList<HashMap<String, Object>> listViewData = new ArrayList<HashMap<String, Object>>(); 291 292 // roll over all known features & check whether device reports them 293 boolean present = false; 294 int statusIcon; 295 Set<Feature> features = new LinkedHashSet<Feature>(); 296 297 // add features from latest to last so that the latest requirements are put in the set first 298 int apiVersion = Build.VERSION.SDK_INT; 299 if (apiVersion >= Build.VERSION_CODES.O) { 300 Collections.addAll(features, ALL_O_FEATURES); 301 } 302 if (apiVersion >= Build.VERSION_CODES.N) { 303 Collections.addAll(features, ALL_NYC_FEATURES); 304 } 305 if (apiVersion >= Build.VERSION_CODES.M) { 306 Collections.addAll(features, ALL_MNC_FEATURES); 307 } 308 if (apiVersion >= Build.VERSION_CODES.LOLLIPOP) { 309 Collections.addAll(features, ALL_LOLLIPOP_FEATURES); 310 } 311 if (apiVersion >= Build.VERSION_CODES.KITKAT_WATCH) { 312 Collections.addAll(features, ALL_KITKAT_WATCH_FEATURES); 313 } 314 if (apiVersion >= Build.VERSION_CODES.KITKAT) { 315 Collections.addAll(features, ALL_KITKAT_FEATURES); 316 } 317 if (apiVersion >= Build.VERSION_CODES.JELLY_BEAN_MR2) { 318 Collections.addAll(features, ALL_JELLY_BEAN_MR2_FEATURES); 319 } 320 if (apiVersion >= Build.VERSION_CODES.JELLY_BEAN) { 321 Collections.addAll(features, ALL_JELLY_BEAN_FEATURES); 322 } 323 if (apiVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 324 Collections.addAll(features, ALL_ICE_CREAM_SANDWICH_FEATURES); 325 } 326 if (apiVersion >= Build.VERSION_CODES.HONEYCOMB_MR2) { 327 Collections.addAll(features, ALL_HONEYCOMB_MR2_FEATURES); 328 } 329 if (apiVersion >= Build.VERSION_CODES.HONEYCOMB_MR1) { 330 Collections.addAll(features, ALL_HONEYCOMB_MR1_FEATURES); 331 } 332 if (apiVersion >= Build.VERSION_CODES.HONEYCOMB) { 333 Collections.addAll(features, ALL_HONEYCOMB_FEATURES); 334 } 335 if (apiVersion >= Build.VERSION_CODES.GINGERBREAD_MR1) { 336 Collections.addAll(features, ALL_GINGERBREAD_MR1_FEATURES); 337 } 338 if (apiVersion >= Build.VERSION_CODES.GINGERBREAD) { 339 Collections.addAll(features, ALL_GINGERBREAD_FEATURES); 340 } 341 if (apiVersion >= Build.VERSION_CODES.FROYO) { 342 Collections.addAll(features, ALL_FROYO_FEATURES); 343 } 344 if (apiVersion >= Build.VERSION_CODES.ECLAIR_MR1) { 345 Collections.addAll(features, ALL_ECLAIR_FEATURES); 346 } 347 348 hasTelevision = getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION); 349 for (Feature f : features) { 350 HashMap<String, Object> row = new HashMap<String, Object>(); 351 listViewData.add(row); 352 present = actualFeatures.containsKey(f.name); 353 if (present) { 354 // device reports it -- yay! set the happy icon 355 hasWifi = hasWifi || PackageManager.FEATURE_WIFI.equals(f.name); 356 hasTelephony = hasTelephony || PackageManager.FEATURE_TELEPHONY.equals(f.name); 357 hasBluetooth = hasBluetooth || PackageManager.FEATURE_BLUETOOTH.equals(f.name); 358 statusIcon = R.drawable.fs_good; 359 actualFeatures.remove(f.name); 360 } else if (!present && f.required) { 361 // it's required, but device doesn't report it. Boo, set the 362 // bogus icon 363 statusIcon = R.drawable.fs_error; 364 if (hasTelevision && PackageManager.FEATURE_LOCATION.equals(f.name)) { 365 statusIcon = R.drawable.fs_indeterminate; 366 } 367 } else { 368 // device doesn't report it, but it's not req'd, so can't tell 369 // if there's a problem 370 statusIcon = R.drawable.fs_indeterminate; 371 } 372 row.put("feature", f.name); 373 row.put("icon", statusIcon); 374 } 375 376 // now roll over any remaining features (which are non-standard) 377 for (String feature : actualFeatures.keySet()) { 378 if (feature == null || "".equals(feature)) 379 continue; 380 HashMap<String, Object> row = new HashMap<String, Object>(); 381 listViewData.add(row); 382 row.put("feature", feature); 383 if (feature.startsWith("android")) { // intentionally not "android." 384 // sorry, you're not allowed to squat in the official namespace; 385 // set bogus icon 386 row.put("icon", R.drawable.fs_error); 387 hasIllegalFeature = true; 388 } else { 389 // non-standard features are okay, but flag them just in case 390 row.put("icon", R.drawable.fs_warning); 391 } 392 } 393 394 // sort the ListView's data to group by icon type, for easier reading by 395 // humans 396 final HashMap<Integer, Integer> idMap = new HashMap<Integer, Integer>(); 397 idMap.put(R.drawable.fs_error, 0); 398 idMap.put(R.drawable.fs_warning, 1); 399 idMap.put(R.drawable.fs_indeterminate, 2); 400 idMap.put(R.drawable.fs_good, 3); 401 Collections.sort(listViewData, new Comparator<HashMap<String, Object>>() { 402 public int compare(HashMap<String, Object> left, HashMap<String, Object> right) { 403 int leftId = idMap.get(left.get("icon")); 404 int rightId = idMap.get(right.get("icon")); 405 if (leftId == rightId) { 406 return ((String) left.get("feature")).compareTo((String) right.get("feature")); 407 } 408 if (leftId < rightId) 409 return -1; 410 return 1; 411 } 412 }); 413 414 // Set up the SimpleAdapter used to populate the ListView 415 SimpleAdapter adapter = new SimpleAdapter(this, listViewData, R.layout.fs_row, 416 new String[] { 417 "feature", "icon" 418 }, new int[] { 419 R.id.fs_feature, R.id.fs_icon 420 }); 421 adapter.setViewBinder(new SimpleAdapter.ViewBinder() { 422 public boolean setViewValue(View view, Object data, String repr) { 423 try { 424 if (view instanceof ImageView) { 425 ((ImageView) view).setImageResource((Integer) data); 426 } else if (view instanceof TextView) { 427 ((TextView) view).setText((String) data); 428 } else { 429 return false; 430 } 431 return true; 432 } catch (ClassCastException e) { 433 return false; 434 } 435 } 436 }); 437 setListAdapter(adapter); 438 439 // finally, check for our second-order error cases and set warning text 440 // if necessary 441 StringBuffer sb = new StringBuffer(); 442 if (hasIllegalFeature) { 443 sb.append(getResources().getString(R.string.fs_disallowed)).append("\n"); 444 } 445 446 if (!hasWifi && !hasTelephony && !hasBluetooth) { 447 sb.append(getResources().getString(R.string.fs_missing_wifi_telephony)).append("\n"); 448 } 449 450 String warnings = sb.toString().trim(); 451 if (warnings == null || "".equals(warnings)) { 452 ((TextView) (findViewById(R.id.fs_warnings))).setVisibility(View.GONE); 453 } else { 454 ((TextView) (findViewById(R.id.fs_warnings))).setText(warnings); 455 } 456 } 457 } 458