2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.settings;
19 import android.app.Activity;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.pm.ApplicationInfo;
23 import android.content.pm.PackageManager;
24 import android.content.pm.ResolveInfo;
25 import android.os.Build;
26 import android.os.Bundle;
27 import android.os.PersistableBundle;
28 import android.os.SELinux;
29 import android.os.SystemClock;
30 import android.os.SystemProperties;
31 import android.os.UserHandle;
32 import android.os.UserManager;
33 import android.preference.Preference;
34 import android.preference.PreferenceGroup;
35 import android.preference.PreferenceScreen;
36 import android.provider.SearchIndexableResource;
37 import android.provider.Settings;
38 import android.telephony.CarrierConfigManager;
39 import android.text.TextUtils;
40 import android.text.format.DateFormat;
41 import android.util.Log;
42 import android.widget.Toast;
44 import com.android.internal.logging.MetricsLogger;
45 import com.android.settings.search.BaseSearchIndexProvider;
46 import com.android.settings.search.Index;
47 import com.android.settings.search.Indexable;
49 import java.io.BufferedReader;
50 import java.io.FileReader;
51 import java.io.IOException;
52 import java.text.ParseException;
53 import java.text.SimpleDateFormat;
54 import java.util.ArrayList;
55 import java.util.Arrays;
56 import java.util.Date;
57 import java.util.List;
58 import java.util.Locale;
59 import java.util.regex.Matcher;
60 import java.util.regex.Pattern;
62 public class DeviceInfoSettings extends SettingsPreferenceFragment implements Indexable {
64 private static final String LOG_TAG = "DeviceInfoSettings";
65 private static final String FILENAME_PROC_VERSION = "/proc/version";
66 private static final String FILENAME_MSV = "/sys/board_properties/soc/msv";
68 private static final String KEY_MANUAL = "manual";
69 private static final String KEY_REGULATORY_INFO = "regulatory_info";
70 private static final String KEY_SYSTEM_UPDATE_SETTINGS = "system_update_settings";
71 private static final String PROPERTY_URL_SAFETYLEGAL = "ro.url.safetylegal";
72 private static final String PROPERTY_SELINUX_STATUS = "ro.build.selinux";
73 private static final String KEY_KERNEL_VERSION = "kernel_version";
74 private static final String KEY_BUILD_NUMBER = "build_number";
75 private static final String KEY_DEVICE_MODEL = "device_model";
76 private static final String KEY_SELINUX_STATUS = "selinux_status";
77 private static final String KEY_BASEBAND_VERSION = "baseband_version";
78 private static final String KEY_FIRMWARE_VERSION = "firmware_version";
79 private static final String KEY_SECURITY_PATCH = "security_patch";
80 private static final String KEY_UPDATE_SETTING = "additional_system_update_settings";
81 private static final String KEY_EQUIPMENT_ID = "fcc_equipment_id";
82 private static final String PROPERTY_EQUIPMENT_ID = "ro.ril.fccid";
83 private static final String KEY_DEVICE_FEEDBACK = "device_feedback";
84 private static final String KEY_SAFETY_LEGAL = "safetylegal";
86 static final int TAPS_TO_BE_A_DEVELOPER = 7;
88 long[] mHits = new long[3];
93 protected int getMetricsCategory() {
94 return MetricsLogger.DEVICEINFO;
98 protected int getHelpResource() {
99 return R.string.help_uri_about;
103 public void onCreate(Bundle icicle) {
104 super.onCreate(icicle);
106 addPreferencesFromResource(R.xml.device_info_settings);
108 setStringSummary(KEY_FIRMWARE_VERSION, Build.VERSION.RELEASE);
109 findPreference(KEY_FIRMWARE_VERSION).setEnabled(true);
110 String patch = Build.VERSION.SECURITY_PATCH;
111 if (!"".equals(patch)) {
113 SimpleDateFormat template = new SimpleDateFormat("yyyy-MM-dd");
114 Date patchDate = template.parse(patch);
115 String format = DateFormat.getBestDateTimePattern(Locale.getDefault(), "dMMMMyyyy");
116 patch = DateFormat.format(format, patchDate).toString();
117 } catch (ParseException e) {
118 // broken parse; fall through and use the raw string
120 setStringSummary(KEY_SECURITY_PATCH, patch);
122 getPreferenceScreen().removePreference(findPreference(KEY_SECURITY_PATCH));
125 setValueSummary(KEY_BASEBAND_VERSION, "gsm.version.baseband");
126 setStringSummary(KEY_DEVICE_MODEL, Build.MODEL + getMsvSuffix());
127 setValueSummary(KEY_EQUIPMENT_ID, PROPERTY_EQUIPMENT_ID);
128 setStringSummary(KEY_DEVICE_MODEL, Build.MODEL);
129 setStringSummary(KEY_BUILD_NUMBER, Build.DISPLAY);
130 findPreference(KEY_BUILD_NUMBER).setEnabled(true);
131 findPreference(KEY_KERNEL_VERSION).setSummary(getFormattedKernelVersion());
133 if (!SELinux.isSELinuxEnabled()) {
134 String status = getResources().getString(R.string.selinux_status_disabled);
135 setStringSummary(KEY_SELINUX_STATUS, status);
136 } else if (!SELinux.isSELinuxEnforced()) {
137 String status = getResources().getString(R.string.selinux_status_permissive);
138 setStringSummary(KEY_SELINUX_STATUS, status);
141 // Remove selinux information if property is not present
142 removePreferenceIfPropertyMissing(getPreferenceScreen(), KEY_SELINUX_STATUS,
143 PROPERTY_SELINUX_STATUS);
145 // Remove Safety information preference if PROPERTY_URL_SAFETYLEGAL is not set
146 removePreferenceIfPropertyMissing(getPreferenceScreen(), KEY_SAFETY_LEGAL,
147 PROPERTY_URL_SAFETYLEGAL);
149 // Remove Equipment id preference if FCC ID is not set by RIL
150 removePreferenceIfPropertyMissing(getPreferenceScreen(), KEY_EQUIPMENT_ID,
151 PROPERTY_EQUIPMENT_ID);
153 // Remove Baseband version if wifi-only device
154 if (Utils.isWifiOnly(getActivity())) {
155 getPreferenceScreen().removePreference(findPreference(KEY_BASEBAND_VERSION));
158 // Dont show feedback option if there is no reporter.
159 if (TextUtils.isEmpty(getFeedbackReporterPackage(getActivity()))) {
160 getPreferenceScreen().removePreference(findPreference(KEY_DEVICE_FEEDBACK));
164 * Settings is a generic app and should not contain any device-specific
167 final Activity act = getActivity();
169 // These are contained by the root preference screen
170 PreferenceGroup parentPreference = getPreferenceScreen();
171 if (UserHandle.myUserId() == UserHandle.USER_OWNER) {
172 Utils.updatePreferenceToSpecificActivityOrRemove(act, parentPreference,
173 KEY_SYSTEM_UPDATE_SETTINGS,
174 Utils.UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY);
176 // Remove for secondary users
177 removePreference(KEY_SYSTEM_UPDATE_SETTINGS);
180 // Read platform settings for additional system update setting
181 removePreferenceIfBoolFalse(KEY_UPDATE_SETTING,
182 R.bool.config_additional_system_update_setting_enable);
184 // Remove manual entry if none present.
185 removePreferenceIfBoolFalse(KEY_MANUAL, R.bool.config_show_manual);
187 // Remove regulatory information if none present.
188 final Intent intent = new Intent(Settings.ACTION_SHOW_REGULATORY_INFO);
189 if (getPackageManager().queryIntentActivities(intent, 0).isEmpty()) {
190 Preference pref = findPreference(KEY_REGULATORY_INFO);
192 getPreferenceScreen().removePreference(pref);
198 public void onResume() {
200 mDevHitCountdown = getActivity().getSharedPreferences(DevelopmentSettings.PREF_FILE,
201 Context.MODE_PRIVATE).getBoolean(DevelopmentSettings.PREF_SHOW,
202 android.os.Build.TYPE.equals("eng")) ? -1 : TAPS_TO_BE_A_DEVELOPER;
207 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
208 if (preference.getKey().equals(KEY_FIRMWARE_VERSION)) {
209 System.arraycopy(mHits, 1, mHits, 0, mHits.length-1);
210 mHits[mHits.length-1] = SystemClock.uptimeMillis();
211 if (mHits[0] >= (SystemClock.uptimeMillis()-500)) {
212 UserManager um = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
213 if (um.hasUserRestriction(UserManager.DISALLOW_FUN)) {
214 Log.d(LOG_TAG, "Sorry, no fun for you!");
218 Intent intent = new Intent(Intent.ACTION_MAIN);
219 intent.setClassName("android",
220 com.android.internal.app.PlatLogoActivity.class.getName());
222 startActivity(intent);
223 } catch (Exception e) {
224 Log.e(LOG_TAG, "Unable to start activity " + intent.toString());
227 } else if (preference.getKey().equals(KEY_BUILD_NUMBER)) {
228 // Don't enable developer options for secondary users.
229 if (UserHandle.myUserId() != UserHandle.USER_OWNER) return true;
231 // Don't enable developer options until device has been provisioned
232 if (Settings.Global.getInt(getActivity().getContentResolver(),
233 Settings.Global.DEVICE_PROVISIONED, 0) == 0) {
237 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
238 if (um.hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES)) return true;
240 if (mDevHitCountdown > 0) {
242 if (mDevHitCountdown == 0) {
243 getActivity().getSharedPreferences(DevelopmentSettings.PREF_FILE,
244 Context.MODE_PRIVATE).edit().putBoolean(
245 DevelopmentSettings.PREF_SHOW, true).apply();
246 if (mDevHitToast != null) {
247 mDevHitToast.cancel();
249 mDevHitToast = Toast.makeText(getActivity(), R.string.show_dev_on,
252 // This is good time to index the Developer Options
254 getActivity().getApplicationContext()).updateFromClassNameResource(
255 DevelopmentSettings.class.getName(), true, true);
257 } else if (mDevHitCountdown > 0
258 && mDevHitCountdown < (TAPS_TO_BE_A_DEVELOPER-2)) {
259 if (mDevHitToast != null) {
260 mDevHitToast.cancel();
262 mDevHitToast = Toast.makeText(getActivity(), getResources().getQuantityString(
263 R.plurals.show_dev_countdown, mDevHitCountdown, mDevHitCountdown),
267 } else if (mDevHitCountdown < 0) {
268 if (mDevHitToast != null) {
269 mDevHitToast.cancel();
271 mDevHitToast = Toast.makeText(getActivity(), R.string.show_dev_already,
275 } else if (preference.getKey().equals(KEY_DEVICE_FEEDBACK)) {
277 } else if(preference.getKey().equals(KEY_SYSTEM_UPDATE_SETTINGS)) {
278 CarrierConfigManager configManager =
279 (CarrierConfigManager) getSystemService(Context.CARRIER_CONFIG_SERVICE);
280 PersistableBundle b = configManager.getConfig();
281 if (b.getBoolean(CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_BOOL)) {
282 ciActionOnSysUpdate(b);
285 return super.onPreferenceTreeClick(preferenceScreen, preference);
289 * Trigger client initiated action (send intent) on system update
291 private void ciActionOnSysUpdate(PersistableBundle b) {
292 String intentStr = b.getString(CarrierConfigManager.
293 KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING);
294 if (!TextUtils.isEmpty(intentStr)) {
295 String extra = b.getString(CarrierConfigManager.
296 KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING);
297 String extraVal = b.getString(CarrierConfigManager.
298 KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING);
300 Intent intent = new Intent(intentStr);
301 if (!TextUtils.isEmpty(extra)) {
302 intent.putExtra(extra, extraVal);
304 Log.d(LOG_TAG, "ciActionOnSysUpdate: broadcasting intent " + intentStr +
305 " with extra " + extra + ", " + extraVal);
306 getActivity().getApplicationContext().sendBroadcast(intent);
310 private void removePreferenceIfPropertyMissing(PreferenceGroup preferenceGroup,
311 String preference, String property ) {
312 if (SystemProperties.get(property).equals("")) {
313 // Property is missing so remove preference from group
315 preferenceGroup.removePreference(findPreference(preference));
316 } catch (RuntimeException e) {
317 Log.d(LOG_TAG, "Property '" + property + "' missing and no '"
318 + preference + "' preference");
323 private void removePreferenceIfBoolFalse(String preference, int resId) {
324 if (!getResources().getBoolean(resId)) {
325 Preference pref = findPreference(preference);
327 getPreferenceScreen().removePreference(pref);
332 private void setStringSummary(String preference, String value) {
334 findPreference(preference).setSummary(value);
335 } catch (RuntimeException e) {
336 findPreference(preference).setSummary(
337 getResources().getString(R.string.device_info_default));
341 private void setValueSummary(String preference, String property) {
343 findPreference(preference).setSummary(
344 SystemProperties.get(property,
345 getResources().getString(R.string.device_info_default)));
346 } catch (RuntimeException e) {
351 private void sendFeedback() {
352 String reporterPackage = getFeedbackReporterPackage(getActivity());
353 if (TextUtils.isEmpty(reporterPackage)) {
356 Intent intent = new Intent(Intent.ACTION_BUG_REPORT);
357 intent.setPackage(reporterPackage);
358 startActivityForResult(intent, 0);
362 * Reads a line from the specified file.
363 * @param filename the file to read from
364 * @return the first line, if any.
365 * @throws IOException if the file couldn't be read
367 private static String readLine(String filename) throws IOException {
368 BufferedReader reader = new BufferedReader(new FileReader(filename), 256);
370 return reader.readLine();
376 public static String getFormattedKernelVersion() {
378 return formatKernelVersion(readLine(FILENAME_PROC_VERSION));
380 } catch (IOException e) {
382 "IO Exception when getting kernel version for Device Info screen",
385 return "Unavailable";
389 public static String formatKernelVersion(String rawKernelVersion) {
390 // Example (see tests for more):
391 // Linux version 3.0.31-g6fb96c9 (android-build@xxx.xxx.xxx.xxx.com) \
392 // (gcc version 4.6.x-xxx 20120106 (prerelease) (GCC) ) #1 SMP PREEMPT \
393 // Thu Jun 28 11:02:39 PDT 2012
395 final String PROC_VERSION_REGEX =
396 "Linux version (\\S+) " + /* group 1: "3.0.31-g6fb96c9" */
397 "\\((\\S+?)\\) " + /* group 2: "x@y.com" (kernel builder) */
398 "(?:\\(gcc.+? \\)) " + /* ignore: GCC version information */
399 "(#\\d+) " + /* group 3: "#1" */
400 "(?:.*?)?" + /* ignore: optional SMP, PREEMPT, and any CONFIG_FLAGS */
401 "((Sun|Mon|Tue|Wed|Thu|Fri|Sat).+)"; /* group 4: "Thu Jun 28 11:02:39 PDT 2012" */
403 Matcher m = Pattern.compile(PROC_VERSION_REGEX).matcher(rawKernelVersion);
405 Log.e(LOG_TAG, "Regex did not match on /proc/version: " + rawKernelVersion);
406 return "Unavailable";
407 } else if (m.groupCount() < 4) {
408 Log.e(LOG_TAG, "Regex match on /proc/version only returned " + m.groupCount()
410 return "Unavailable";
412 return m.group(1) + "\n" + // 3.0.31-g6fb96c9
413 m.group(2) + " " + m.group(3) + "\n" + // x@y.com #1
414 m.group(4); // Thu Jun 28 11:02:39 PDT 2012
418 * Returns " (ENGINEERING)" if the msv file has a zero value, else returns "".
419 * @return a string to append to the model number description.
421 private String getMsvSuffix() {
422 // Production devices should have a non-zero value. If we can't read it, assume it's a
423 // production device so that we don't accidentally show that it's an ENGINEERING device.
425 String msv = readLine(FILENAME_MSV);
426 // Parse as a hex number. If it evaluates to a zero, then it's an engineering build.
427 if (Long.parseLong(msv, 16) == 0) {
428 return " (ENGINEERING)";
430 } catch (IOException ioe) {
431 // Fail quietly, as the file may not exist on some devices.
432 } catch (NumberFormatException nfe) {
433 // Fail quietly, returning empty string should be sufficient
438 private static String getFeedbackReporterPackage(Context context) {
439 final String feedbackReporter =
440 context.getResources().getString(R.string.oem_preferred_feedback_reporter);
441 if (TextUtils.isEmpty(feedbackReporter)) {
442 // Reporter not configured. Return.
443 return feedbackReporter;
445 // Additional checks to ensure the reporter is on system image, and reporter is
446 // configured to listen to the intent. Otherwise, dont show the "send feedback" option.
447 final Intent intent = new Intent(Intent.ACTION_BUG_REPORT);
449 PackageManager pm = context.getPackageManager();
450 List<ResolveInfo> resolvedPackages =
451 pm.queryIntentActivities(intent, PackageManager.GET_RESOLVED_FILTER);
452 for (ResolveInfo info : resolvedPackages) {
453 if (info.activityInfo != null) {
454 if (!TextUtils.isEmpty(info.activityInfo.packageName)) {
456 ApplicationInfo ai = pm.getApplicationInfo(info.activityInfo.packageName, 0);
457 if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
458 // Package is on the system image
459 if (TextUtils.equals(
460 info.activityInfo.packageName, feedbackReporter)) {
461 return feedbackReporter;
464 } catch (PackageManager.NameNotFoundException e) {
465 // No need to do anything here.
476 public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
477 new BaseSearchIndexProvider() {
480 public List<SearchIndexableResource> getXmlResourcesToIndex(
481 Context context, boolean enabled) {
482 final SearchIndexableResource sir = new SearchIndexableResource(context);
483 sir.xmlResId = R.xml.device_info_settings;
484 return Arrays.asList(sir);
488 public List<String> getNonIndexableKeys(Context context) {
489 final List<String> keys = new ArrayList<String>();
490 if (isPropertyMissing(PROPERTY_SELINUX_STATUS)) {
491 keys.add(KEY_SELINUX_STATUS);
493 if (isPropertyMissing(PROPERTY_URL_SAFETYLEGAL)) {
494 keys.add(KEY_SAFETY_LEGAL);
496 if (isPropertyMissing(PROPERTY_EQUIPMENT_ID)) {
497 keys.add(KEY_EQUIPMENT_ID);
499 // Remove Baseband version if wifi-only device
500 if (Utils.isWifiOnly(context)) {
501 keys.add((KEY_BASEBAND_VERSION));
503 // Dont show feedback option if there is no reporter.
504 if (TextUtils.isEmpty(getFeedbackReporterPackage(context))) {
505 keys.add(KEY_DEVICE_FEEDBACK);
507 if (UserHandle.myUserId() != UserHandle.USER_OWNER) {
508 keys.add(KEY_SYSTEM_UPDATE_SETTINGS);
510 if (!context.getResources().getBoolean(
511 R.bool.config_additional_system_update_setting_enable)) {
512 keys.add(KEY_UPDATE_SETTING);
517 private boolean isPropertyMissing(String property) {
518 return SystemProperties.get(property).equals("");