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