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