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