1 /** 2 * Copyright (C) 2007 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations 14 * under the License. 15 */ 16 17 package com.android.settings; 18 19 import android.app.Activity; 20 import android.app.ActivityManager; 21 import android.app.AlertDialog; 22 import android.app.Dialog; 23 import android.content.ContentResolver; 24 import android.content.Context; 25 import android.content.DialogInterface; 26 import android.content.Intent; 27 import android.content.pm.ApplicationInfo; 28 import android.content.pm.PackageManager; 29 import android.content.pm.PackageManager.NameNotFoundException; 30 import android.content.pm.ResolveInfo; 31 import android.content.pm.UserInfo; 32 import android.content.res.Resources; 33 import android.content.res.Resources.NotFoundException; 34 import android.database.Cursor; 35 import android.graphics.Bitmap; 36 import android.graphics.BitmapFactory; 37 import android.graphics.drawable.Drawable; 38 import android.net.ConnectivityManager; 39 import android.net.LinkProperties; 40 import android.net.Uri; 41 import android.os.BatteryManager; 42 import android.os.Bundle; 43 import android.os.ParcelFileDescriptor; 44 import android.os.UserHandle; 45 import android.os.UserManager; 46 import android.preference.Preference; 47 import android.preference.PreferenceActivity.Header; 48 import android.preference.PreferenceFrameLayout; 49 import android.preference.PreferenceGroup; 50 import android.provider.ContactsContract.CommonDataKinds; 51 import android.provider.ContactsContract.CommonDataKinds.Phone; 52 import android.provider.ContactsContract.CommonDataKinds.StructuredName; 53 import android.provider.ContactsContract.Contacts; 54 import android.provider.ContactsContract.Data; 55 import android.provider.ContactsContract.Profile; 56 import android.provider.ContactsContract.RawContacts; 57 import android.telephony.TelephonyManager; 58 import android.text.TextUtils; 59 import android.util.Log; 60 import android.view.View; 61 import android.view.ViewGroup; 62 import android.widget.ListView; 63 import android.widget.TabWidget; 64 65 import com.android.settings.users.ProfileUpdateReceiver; 66 67 import java.io.FileOutputStream; 68 import java.io.IOException; 69 import java.io.InputStream; 70 import java.net.InetAddress; 71 import java.util.Iterator; 72 import java.util.List; 73 import java.util.Locale; 74 75 public class Utils { 76 77 /** 78 * Set the preference's title to the matching activity's label. 79 */ 80 public static final int UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY = 1; 81 82 /** 83 * Name of the meta-data item that should be set in the AndroidManifest.xml 84 * to specify the icon that should be displayed for the preference. 85 */ 86 private static final String META_DATA_PREFERENCE_ICON = "com.android.settings.icon"; 87 88 /** 89 * Name of the meta-data item that should be set in the AndroidManifest.xml 90 * to specify the title that should be displayed for the preference. 91 */ 92 private static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title"; 93 94 /** 95 * Name of the meta-data item that should be set in the AndroidManifest.xml 96 * to specify the summary text that should be displayed for the preference. 97 */ 98 private static final String META_DATA_PREFERENCE_SUMMARY = "com.android.settings.summary"; 99 100 /** 101 * Finds a matching activity for a preference's intent. If a matching 102 * activity is not found, it will remove the preference. 103 * 104 * @param context The context. 105 * @param parentPreferenceGroup The preference group that contains the 106 * preference whose intent is being resolved. 107 * @param preferenceKey The key of the preference whose intent is being 108 * resolved. 109 * @param flags 0 or one or more of 110 * {@link #UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY} 111 * . 112 * @return Whether an activity was found. If false, the preference was 113 * removed. 114 */ updatePreferenceToSpecificActivityOrRemove(Context context, PreferenceGroup parentPreferenceGroup, String preferenceKey, int flags)115 public static boolean updatePreferenceToSpecificActivityOrRemove(Context context, 116 PreferenceGroup parentPreferenceGroup, String preferenceKey, int flags) { 117 118 Preference preference = parentPreferenceGroup.findPreference(preferenceKey); 119 if (preference == null) { 120 return false; 121 } 122 123 Intent intent = preference.getIntent(); 124 if (intent != null) { 125 // Find the activity that is in the system image 126 PackageManager pm = context.getPackageManager(); 127 List<ResolveInfo> list = pm.queryIntentActivities(intent, 0); 128 int listSize = list.size(); 129 for (int i = 0; i < listSize; i++) { 130 ResolveInfo resolveInfo = list.get(i); 131 if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) 132 != 0) { 133 134 // Replace the intent with this specific activity 135 preference.setIntent(new Intent().setClassName( 136 resolveInfo.activityInfo.packageName, 137 resolveInfo.activityInfo.name)); 138 139 if ((flags & UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY) != 0) { 140 // Set the preference title to the activity's label 141 preference.setTitle(resolveInfo.loadLabel(pm)); 142 } 143 144 return true; 145 } 146 } 147 } 148 149 // Did not find a matching activity, so remove the preference 150 parentPreferenceGroup.removePreference(preference); 151 152 return false; 153 } 154 155 /** 156 * Finds a matching activity for a preference's intent. If a matching 157 * activity is not found, it will remove the preference. The icon, title and 158 * summary of the preference will also be updated with the values retrieved 159 * from the activity's meta-data elements. If no meta-data elements are 160 * specified then the preference title will be set to match the label of the 161 * activity, an icon and summary text will not be displayed. 162 * 163 * @param context The context. 164 * @param parentPreferenceGroup The preference group that contains the 165 * preference whose intent is being resolved. 166 * @param preferenceKey The key of the preference whose intent is being 167 * resolved. 168 * 169 * @return Whether an activity was found. If false, the preference was 170 * removed. 171 * 172 * @see {@link #META_DATA_PREFERENCE_ICON} 173 * {@link #META_DATA_PREFERENCE_TITLE} 174 * {@link #META_DATA_PREFERENCE_SUMMARY} 175 */ updatePreferenceToSpecificActivityFromMetaDataOrRemove(Context context, PreferenceGroup parentPreferenceGroup, String preferenceKey)176 public static boolean updatePreferenceToSpecificActivityFromMetaDataOrRemove(Context context, 177 PreferenceGroup parentPreferenceGroup, String preferenceKey) { 178 179 IconPreferenceScreen preference = (IconPreferenceScreen)parentPreferenceGroup 180 .findPreference(preferenceKey); 181 if (preference == null) { 182 return false; 183 } 184 185 Intent intent = preference.getIntent(); 186 if (intent != null) { 187 // Find the activity that is in the system image 188 PackageManager pm = context.getPackageManager(); 189 List<ResolveInfo> list = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA); 190 int listSize = list.size(); 191 for (int i = 0; i < listSize; i++) { 192 ResolveInfo resolveInfo = list.get(i); 193 if ((resolveInfo.activityInfo.applicationInfo.flags 194 & ApplicationInfo.FLAG_SYSTEM) != 0) { 195 Drawable icon = null; 196 String title = null; 197 String summary = null; 198 199 // Get the activity's meta-data 200 try { 201 Resources res = pm 202 .getResourcesForApplication(resolveInfo.activityInfo.packageName); 203 Bundle metaData = resolveInfo.activityInfo.metaData; 204 205 if (res != null && metaData != null) { 206 icon = res.getDrawable(metaData.getInt(META_DATA_PREFERENCE_ICON)); 207 title = res.getString(metaData.getInt(META_DATA_PREFERENCE_TITLE)); 208 summary = res.getString(metaData.getInt(META_DATA_PREFERENCE_SUMMARY)); 209 } 210 } catch (NameNotFoundException e) { 211 // Ignore 212 } catch (NotFoundException e) { 213 // Ignore 214 } 215 216 // Set the preference title to the activity's label if no 217 // meta-data is found 218 if (TextUtils.isEmpty(title)) { 219 title = resolveInfo.loadLabel(pm).toString(); 220 } 221 222 // Set icon, title and summary for the preference 223 preference.setIcon(icon); 224 preference.setTitle(title); 225 preference.setSummary(summary); 226 227 // Replace the intent with this specific activity 228 preference.setIntent(new Intent().setClassName( 229 resolveInfo.activityInfo.packageName, 230 resolveInfo.activityInfo.name)); 231 232 return true; 233 } 234 } 235 } 236 237 // Did not find a matching activity, so remove the preference 238 parentPreferenceGroup.removePreference(preference); 239 240 return false; 241 } 242 updateHeaderToSpecificActivityFromMetaDataOrRemove(Context context, List<Header> target, Header header)243 public static boolean updateHeaderToSpecificActivityFromMetaDataOrRemove(Context context, 244 List<Header> target, Header header) { 245 246 Intent intent = header.intent; 247 if (intent != null) { 248 // Find the activity that is in the system image 249 PackageManager pm = context.getPackageManager(); 250 List<ResolveInfo> list = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA); 251 int listSize = list.size(); 252 for (int i = 0; i < listSize; i++) { 253 ResolveInfo resolveInfo = list.get(i); 254 if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) 255 != 0) { 256 Drawable icon = null; 257 String title = null; 258 String summary = null; 259 260 // Get the activity's meta-data 261 try { 262 Resources res = pm.getResourcesForApplication( 263 resolveInfo.activityInfo.packageName); 264 Bundle metaData = resolveInfo.activityInfo.metaData; 265 266 if (res != null && metaData != null) { 267 icon = res.getDrawable(metaData.getInt(META_DATA_PREFERENCE_ICON)); 268 title = res.getString(metaData.getInt(META_DATA_PREFERENCE_TITLE)); 269 summary = res.getString(metaData.getInt(META_DATA_PREFERENCE_SUMMARY)); 270 } 271 } catch (NameNotFoundException e) { 272 // Ignore 273 } catch (NotFoundException e) { 274 // Ignore 275 } 276 277 // Set the preference title to the activity's label if no 278 // meta-data is found 279 if (TextUtils.isEmpty(title)) { 280 title = resolveInfo.loadLabel(pm).toString(); 281 } 282 283 // Set icon, title and summary for the preference 284 // TODO: 285 //header.icon = icon; 286 header.title = title; 287 header.summary = summary; 288 // Replace the intent with this specific activity 289 header.intent = new Intent().setClassName(resolveInfo.activityInfo.packageName, 290 resolveInfo.activityInfo.name); 291 292 return true; 293 } 294 } 295 } 296 297 // Did not find a matching activity, so remove the preference 298 target.remove(header); 299 300 return false; 301 } 302 303 /** 304 * Returns true if Monkey is running. 305 */ isMonkeyRunning()306 public static boolean isMonkeyRunning() { 307 return ActivityManager.isUserAMonkey(); 308 } 309 310 /** 311 * Returns whether the device is voice-capable (meaning, it is also a phone). 312 */ isVoiceCapable(Context context)313 public static boolean isVoiceCapable(Context context) { 314 TelephonyManager telephony = 315 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 316 return telephony != null && telephony.isVoiceCapable(); 317 } 318 isWifiOnly(Context context)319 public static boolean isWifiOnly(Context context) { 320 ConnectivityManager cm = (ConnectivityManager)context.getSystemService( 321 Context.CONNECTIVITY_SERVICE); 322 return (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false); 323 } 324 325 /** 326 * Returns the WIFI IP Addresses, if any, taking into account IPv4 and IPv6 style addresses. 327 * @param context the application context 328 * @return the formatted and comma-separated IP addresses, or null if none. 329 */ getWifiIpAddresses(Context context)330 public static String getWifiIpAddresses(Context context) { 331 ConnectivityManager cm = (ConnectivityManager) 332 context.getSystemService(Context.CONNECTIVITY_SERVICE); 333 LinkProperties prop = cm.getLinkProperties(ConnectivityManager.TYPE_WIFI); 334 return formatIpAddresses(prop); 335 } 336 337 /** 338 * Returns the default link's IP addresses, if any, taking into account IPv4 and IPv6 style 339 * addresses. 340 * @param context the application context 341 * @return the formatted and comma-separated IP addresses, or null if none. 342 */ getDefaultIpAddresses(Context context)343 public static String getDefaultIpAddresses(Context context) { 344 ConnectivityManager cm = (ConnectivityManager) 345 context.getSystemService(Context.CONNECTIVITY_SERVICE); 346 LinkProperties prop = cm.getActiveLinkProperties(); 347 return formatIpAddresses(prop); 348 } 349 formatIpAddresses(LinkProperties prop)350 private static String formatIpAddresses(LinkProperties prop) { 351 if (prop == null) return null; 352 Iterator<InetAddress> iter = prop.getAddresses().iterator(); 353 // If there are no entries, return null 354 if (!iter.hasNext()) return null; 355 // Concatenate all available addresses, comma separated 356 String addresses = ""; 357 while (iter.hasNext()) { 358 addresses += iter.next().getHostAddress(); 359 if (iter.hasNext()) addresses += ", "; 360 } 361 return addresses; 362 } 363 createLocaleFromString(String localeStr)364 public static Locale createLocaleFromString(String localeStr) { 365 // TODO: is there a better way to actually construct a locale that will match? 366 // The main problem is, on top of Java specs, locale.toString() and 367 // new Locale(locale.toString()).toString() do not return equal() strings in 368 // many cases, because the constructor takes the only string as the language 369 // code. So : new Locale("en", "US").toString() => "en_US" 370 // And : new Locale("en_US").toString() => "en_us" 371 if (null == localeStr) 372 return Locale.getDefault(); 373 String[] brokenDownLocale = localeStr.split("_", 3); 374 // split may not return a 0-length array. 375 if (1 == brokenDownLocale.length) { 376 return new Locale(brokenDownLocale[0]); 377 } else if (2 == brokenDownLocale.length) { 378 return new Locale(brokenDownLocale[0], brokenDownLocale[1]); 379 } else { 380 return new Locale(brokenDownLocale[0], brokenDownLocale[1], brokenDownLocale[2]); 381 } 382 } 383 getBatteryPercentage(Intent batteryChangedIntent)384 public static String getBatteryPercentage(Intent batteryChangedIntent) { 385 int level = batteryChangedIntent.getIntExtra("level", 0); 386 int scale = batteryChangedIntent.getIntExtra("scale", 100); 387 return String.valueOf(level * 100 / scale) + "%"; 388 } 389 getBatteryStatus(Resources res, Intent batteryChangedIntent)390 public static String getBatteryStatus(Resources res, Intent batteryChangedIntent) { 391 final Intent intent = batteryChangedIntent; 392 393 int plugType = intent.getIntExtra("plugged", 0); 394 int status = intent.getIntExtra("status", BatteryManager.BATTERY_STATUS_UNKNOWN); 395 String statusString; 396 if (status == BatteryManager.BATTERY_STATUS_CHARGING) { 397 statusString = res.getString(R.string.battery_info_status_charging); 398 if (plugType > 0) { 399 int resId; 400 if (plugType == BatteryManager.BATTERY_PLUGGED_AC) { 401 resId = R.string.battery_info_status_charging_ac; 402 } else if (plugType == BatteryManager.BATTERY_PLUGGED_USB) { 403 resId = R.string.battery_info_status_charging_usb; 404 } else { 405 resId = R.string.battery_info_status_charging_wireless; 406 } 407 statusString = statusString + " " + res.getString(resId); 408 } 409 } else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) { 410 statusString = res.getString(R.string.battery_info_status_discharging); 411 } else if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING) { 412 statusString = res.getString(R.string.battery_info_status_not_charging); 413 } else if (status == BatteryManager.BATTERY_STATUS_FULL) { 414 statusString = res.getString(R.string.battery_info_status_full); 415 } else { 416 statusString = res.getString(R.string.battery_info_status_unknown); 417 } 418 419 return statusString; 420 } 421 forcePrepareCustomPreferencesList( ViewGroup parent, View child, ListView list, boolean ignoreSidePadding)422 public static void forcePrepareCustomPreferencesList( 423 ViewGroup parent, View child, ListView list, boolean ignoreSidePadding) { 424 list.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY); 425 list.setClipToPadding(false); 426 prepareCustomPreferencesList(parent, child, list, ignoreSidePadding); 427 } 428 429 /** 430 * Prepare a custom preferences layout, moving padding to {@link ListView} 431 * when outside scrollbars are requested. Usually used to display 432 * {@link ListView} and {@link TabWidget} with correct padding. 433 */ prepareCustomPreferencesList( ViewGroup parent, View child, View list, boolean ignoreSidePadding)434 public static void prepareCustomPreferencesList( 435 ViewGroup parent, View child, View list, boolean ignoreSidePadding) { 436 final boolean movePadding = list.getScrollBarStyle() == View.SCROLLBARS_OUTSIDE_OVERLAY; 437 if (movePadding && parent instanceof PreferenceFrameLayout) { 438 ((PreferenceFrameLayout.LayoutParams) child.getLayoutParams()).removeBorders = true; 439 440 final Resources res = list.getResources(); 441 final int paddingSide = res.getDimensionPixelSize( 442 com.android.internal.R.dimen.preference_fragment_padding_side); 443 final int paddingBottom = res.getDimensionPixelSize( 444 com.android.internal.R.dimen.preference_fragment_padding_bottom); 445 446 final int effectivePaddingSide = ignoreSidePadding ? 0 : paddingSide; 447 list.setPadding(effectivePaddingSide, 0, effectivePaddingSide, paddingBottom); 448 } 449 } 450 451 /** 452 * Return string resource that best describes combination of tethering 453 * options available on this device. 454 */ getTetheringLabel(ConnectivityManager cm)455 public static int getTetheringLabel(ConnectivityManager cm) { 456 String[] usbRegexs = cm.getTetherableUsbRegexs(); 457 String[] wifiRegexs = cm.getTetherableWifiRegexs(); 458 String[] bluetoothRegexs = cm.getTetherableBluetoothRegexs(); 459 460 boolean usbAvailable = usbRegexs.length != 0; 461 boolean wifiAvailable = wifiRegexs.length != 0; 462 boolean bluetoothAvailable = bluetoothRegexs.length != 0; 463 464 if (wifiAvailable && usbAvailable && bluetoothAvailable) { 465 return R.string.tether_settings_title_all; 466 } else if (wifiAvailable && usbAvailable) { 467 return R.string.tether_settings_title_all; 468 } else if (wifiAvailable && bluetoothAvailable) { 469 return R.string.tether_settings_title_all; 470 } else if (wifiAvailable) { 471 return R.string.tether_settings_title_wifi; 472 } else if (usbAvailable && bluetoothAvailable) { 473 return R.string.tether_settings_title_usb_bluetooth; 474 } else if (usbAvailable) { 475 return R.string.tether_settings_title_usb; 476 } else { 477 return R.string.tether_settings_title_bluetooth; 478 } 479 } 480 481 /* Used by UserSettings as well. Call this on a non-ui thread. */ copyMeProfilePhoto(Context context, UserInfo user)482 public static boolean copyMeProfilePhoto(Context context, UserInfo user) { 483 Uri contactUri = Profile.CONTENT_URI; 484 485 InputStream avatarDataStream = Contacts.openContactPhotoInputStream( 486 context.getContentResolver(), 487 contactUri, true); 488 // If there's no profile photo, assign a default avatar 489 if (avatarDataStream == null) { 490 return false; 491 } 492 int userId = user != null ? user.id : UserHandle.myUserId(); 493 UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); 494 Bitmap icon = BitmapFactory.decodeStream(avatarDataStream); 495 um.setUserIcon(userId, icon); 496 try { 497 avatarDataStream.close(); 498 } catch (IOException ioe) { } 499 return true; 500 } 501 getMeProfileName(Context context, boolean full)502 public static String getMeProfileName(Context context, boolean full) { 503 if (full) { 504 return getProfileDisplayName(context); 505 } else { 506 return getShorterNameIfPossible(context); 507 } 508 } 509 getShorterNameIfPossible(Context context)510 private static String getShorterNameIfPossible(Context context) { 511 final String given = getLocalProfileGivenName(context); 512 return !TextUtils.isEmpty(given) ? given : getProfileDisplayName(context); 513 } 514 getLocalProfileGivenName(Context context)515 private static String getLocalProfileGivenName(Context context) { 516 final ContentResolver cr = context.getContentResolver(); 517 518 // Find the raw contact ID for the local ME profile raw contact. 519 final long localRowProfileId; 520 final Cursor localRawProfile = cr.query( 521 Profile.CONTENT_RAW_CONTACTS_URI, 522 new String[] {RawContacts._ID}, 523 RawContacts.ACCOUNT_TYPE + " IS NULL AND " + 524 RawContacts.ACCOUNT_NAME + " IS NULL", 525 null, null); 526 if (localRawProfile == null) return null; 527 528 try { 529 if (!localRawProfile.moveToFirst()) { 530 return null; 531 } 532 localRowProfileId = localRawProfile.getLong(0); 533 } finally { 534 localRawProfile.close(); 535 } 536 537 // Find the structured name for the raw contact. 538 final Cursor structuredName = cr.query( 539 Profile.CONTENT_URI.buildUpon().appendPath(Contacts.Data.CONTENT_DIRECTORY).build(), 540 new String[] {CommonDataKinds.StructuredName.GIVEN_NAME, 541 CommonDataKinds.StructuredName.FAMILY_NAME}, 542 Data.RAW_CONTACT_ID + "=" + localRowProfileId, 543 null, null); 544 if (structuredName == null) return null; 545 546 try { 547 if (!structuredName.moveToFirst()) { 548 return null; 549 } 550 String partialName = structuredName.getString(0); 551 if (TextUtils.isEmpty(partialName)) { 552 partialName = structuredName.getString(1); 553 } 554 return partialName; 555 } finally { 556 structuredName.close(); 557 } 558 } 559 getProfileDisplayName(Context context)560 private static final String getProfileDisplayName(Context context) { 561 final ContentResolver cr = context.getContentResolver(); 562 final Cursor profile = cr.query(Profile.CONTENT_URI, 563 new String[] {Profile.DISPLAY_NAME}, null, null, null); 564 if (profile == null) return null; 565 566 try { 567 if (!profile.moveToFirst()) { 568 return null; 569 } 570 return profile.getString(0); 571 } finally { 572 profile.close(); 573 } 574 } 575 576 /** Not global warming, it's global change warning. */ buildGlobalChangeWarningDialog(final Context context, int titleResId, final Runnable positiveAction)577 public static Dialog buildGlobalChangeWarningDialog(final Context context, int titleResId, 578 final Runnable positiveAction) { 579 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 580 builder.setTitle(titleResId); 581 builder.setMessage(R.string.global_change_warning); 582 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 583 @Override 584 public void onClick(DialogInterface dialog, int which) { 585 positiveAction.run(); 586 } 587 }); 588 builder.setNegativeButton(android.R.string.cancel, null); 589 590 return builder.create(); 591 } 592 hasMultipleUsers(Context context)593 public static boolean hasMultipleUsers(Context context) { 594 return ((UserManager) context.getSystemService(Context.USER_SERVICE)) 595 .getUsers().size() > 1; 596 } 597 } 598