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 17package com.android.settings; 18 19import android.app.Activity; 20import android.app.ActivityManager; 21import android.app.AlertDialog; 22import android.app.Dialog; 23import android.content.ContentResolver; 24import android.content.Context; 25import android.content.DialogInterface; 26import android.content.Intent; 27import android.content.pm.ApplicationInfo; 28import android.content.pm.PackageManager; 29import android.content.pm.PackageManager.NameNotFoundException; 30import android.content.pm.ResolveInfo; 31import android.content.pm.UserInfo; 32import android.content.res.Resources; 33import android.content.res.Resources.NotFoundException; 34import android.database.Cursor; 35import android.graphics.Bitmap; 36import android.graphics.BitmapFactory; 37import android.graphics.drawable.Drawable; 38import android.net.ConnectivityManager; 39import android.net.LinkProperties; 40import android.net.Uri; 41import android.os.BatteryManager; 42import android.os.Bundle; 43import android.os.ParcelFileDescriptor; 44import android.os.UserHandle; 45import android.os.UserManager; 46import android.preference.Preference; 47import android.preference.PreferenceActivity.Header; 48import android.preference.PreferenceFrameLayout; 49import android.preference.PreferenceGroup; 50import android.provider.ContactsContract.CommonDataKinds; 51import android.provider.ContactsContract.CommonDataKinds.Phone; 52import android.provider.ContactsContract.CommonDataKinds.StructuredName; 53import android.provider.ContactsContract.Contacts; 54import android.provider.ContactsContract.Data; 55import android.provider.ContactsContract.Profile; 56import android.provider.ContactsContract.RawContacts; 57import android.telephony.TelephonyManager; 58import android.text.TextUtils; 59import android.util.Log; 60import android.view.View; 61import android.view.ViewGroup; 62import android.widget.ListView; 63import android.widget.TabWidget; 64 65import com.android.settings.users.ProfileUpdateReceiver; 66 67import java.io.FileOutputStream; 68import java.io.IOException; 69import java.io.InputStream; 70import java.net.InetAddress; 71import java.util.Iterator; 72import java.util.List; 73import java.util.Locale; 74 75public 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 comma-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 comma-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.getAddresses().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 += ", "; 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 String getBatteryPercentage(Intent batteryChangedIntent) { 390 int level = batteryChangedIntent.getIntExtra("level", 0); 391 int scale = batteryChangedIntent.getIntExtra("scale", 100); 392 return String.valueOf(level * 100 / scale) + "%"; 393 } 394 395 public static String getBatteryStatus(Resources res, Intent batteryChangedIntent) { 396 final Intent intent = batteryChangedIntent; 397 398 int plugType = intent.getIntExtra("plugged", 0); 399 int status = intent.getIntExtra("status", BatteryManager.BATTERY_STATUS_UNKNOWN); 400 String statusString; 401 if (status == BatteryManager.BATTERY_STATUS_CHARGING) { 402 statusString = res.getString(R.string.battery_info_status_charging); 403 if (plugType > 0) { 404 int resId; 405 if (plugType == BatteryManager.BATTERY_PLUGGED_AC) { 406 resId = R.string.battery_info_status_charging_ac; 407 } else if (plugType == BatteryManager.BATTERY_PLUGGED_USB) { 408 resId = R.string.battery_info_status_charging_usb; 409 } else { 410 resId = R.string.battery_info_status_charging_wireless; 411 } 412 statusString = statusString + " " + res.getString(resId); 413 } 414 } else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) { 415 statusString = res.getString(R.string.battery_info_status_discharging); 416 } else if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING) { 417 statusString = res.getString(R.string.battery_info_status_not_charging); 418 } else if (status == BatteryManager.BATTERY_STATUS_FULL) { 419 statusString = res.getString(R.string.battery_info_status_full); 420 } else { 421 statusString = res.getString(R.string.battery_info_status_unknown); 422 } 423 424 return statusString; 425 } 426 427 public static void forcePrepareCustomPreferencesList( 428 ViewGroup parent, View child, ListView list, boolean ignoreSidePadding) { 429 list.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY); 430 list.setClipToPadding(false); 431 prepareCustomPreferencesList(parent, child, list, ignoreSidePadding); 432 } 433 434 /** 435 * Prepare a custom preferences layout, moving padding to {@link ListView} 436 * when outside scrollbars are requested. Usually used to display 437 * {@link ListView} and {@link TabWidget} with correct padding. 438 */ 439 public static void prepareCustomPreferencesList( 440 ViewGroup parent, View child, View list, boolean ignoreSidePadding) { 441 final boolean movePadding = list.getScrollBarStyle() == View.SCROLLBARS_OUTSIDE_OVERLAY; 442 if (movePadding && parent instanceof PreferenceFrameLayout) { 443 ((PreferenceFrameLayout.LayoutParams) child.getLayoutParams()).removeBorders = true; 444 445 final Resources res = list.getResources(); 446 final int paddingSide = res.getDimensionPixelSize( 447 com.android.internal.R.dimen.preference_fragment_padding_side); 448 final int paddingBottom = res.getDimensionPixelSize( 449 com.android.internal.R.dimen.preference_fragment_padding_bottom); 450 451 final int effectivePaddingSide = ignoreSidePadding ? 0 : paddingSide; 452 list.setPaddingRelative(effectivePaddingSide, 0, effectivePaddingSide, paddingBottom); 453 } 454 } 455 456 /** 457 * Return string resource that best describes combination of tethering 458 * options available on this device. 459 */ 460 public static int getTetheringLabel(ConnectivityManager cm) { 461 String[] usbRegexs = cm.getTetherableUsbRegexs(); 462 String[] wifiRegexs = cm.getTetherableWifiRegexs(); 463 String[] bluetoothRegexs = cm.getTetherableBluetoothRegexs(); 464 465 boolean usbAvailable = usbRegexs.length != 0; 466 boolean wifiAvailable = wifiRegexs.length != 0; 467 boolean bluetoothAvailable = bluetoothRegexs.length != 0; 468 469 if (wifiAvailable && usbAvailable && bluetoothAvailable) { 470 return R.string.tether_settings_title_all; 471 } else if (wifiAvailable && usbAvailable) { 472 return R.string.tether_settings_title_all; 473 } else if (wifiAvailable && bluetoothAvailable) { 474 return R.string.tether_settings_title_all; 475 } else if (wifiAvailable) { 476 return R.string.tether_settings_title_wifi; 477 } else if (usbAvailable && bluetoothAvailable) { 478 return R.string.tether_settings_title_usb_bluetooth; 479 } else if (usbAvailable) { 480 return R.string.tether_settings_title_usb; 481 } else { 482 return R.string.tether_settings_title_bluetooth; 483 } 484 } 485 486 /* Used by UserSettings as well. Call this on a non-ui thread. */ 487 public static boolean copyMeProfilePhoto(Context context, UserInfo user) { 488 Uri contactUri = Profile.CONTENT_URI; 489 490 InputStream avatarDataStream = Contacts.openContactPhotoInputStream( 491 context.getContentResolver(), 492 contactUri, true); 493 // If there's no profile photo, assign a default avatar 494 if (avatarDataStream == null) { 495 return false; 496 } 497 int userId = user != null ? user.id : UserHandle.myUserId(); 498 UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); 499 Bitmap icon = BitmapFactory.decodeStream(avatarDataStream); 500 um.setUserIcon(userId, icon); 501 try { 502 avatarDataStream.close(); 503 } catch (IOException ioe) { } 504 return true; 505 } 506 507 public static String getMeProfileName(Context context, boolean full) { 508 if (full) { 509 return getProfileDisplayName(context); 510 } else { 511 return getShorterNameIfPossible(context); 512 } 513 } 514 515 private static String getShorterNameIfPossible(Context context) { 516 final String given = getLocalProfileGivenName(context); 517 return !TextUtils.isEmpty(given) ? given : getProfileDisplayName(context); 518 } 519 520 private static String getLocalProfileGivenName(Context context) { 521 final ContentResolver cr = context.getContentResolver(); 522 523 // Find the raw contact ID for the local ME profile raw contact. 524 final long localRowProfileId; 525 final Cursor localRawProfile = cr.query( 526 Profile.CONTENT_RAW_CONTACTS_URI, 527 new String[] {RawContacts._ID}, 528 RawContacts.ACCOUNT_TYPE + " IS NULL AND " + 529 RawContacts.ACCOUNT_NAME + " IS NULL", 530 null, null); 531 if (localRawProfile == null) return null; 532 533 try { 534 if (!localRawProfile.moveToFirst()) { 535 return null; 536 } 537 localRowProfileId = localRawProfile.getLong(0); 538 } finally { 539 localRawProfile.close(); 540 } 541 542 // Find the structured name for the raw contact. 543 final Cursor structuredName = cr.query( 544 Profile.CONTENT_URI.buildUpon().appendPath(Contacts.Data.CONTENT_DIRECTORY).build(), 545 new String[] {CommonDataKinds.StructuredName.GIVEN_NAME, 546 CommonDataKinds.StructuredName.FAMILY_NAME}, 547 Data.RAW_CONTACT_ID + "=" + localRowProfileId, 548 null, null); 549 if (structuredName == null) return null; 550 551 try { 552 if (!structuredName.moveToFirst()) { 553 return null; 554 } 555 String partialName = structuredName.getString(0); 556 if (TextUtils.isEmpty(partialName)) { 557 partialName = structuredName.getString(1); 558 } 559 return partialName; 560 } finally { 561 structuredName.close(); 562 } 563 } 564 565 private static final String getProfileDisplayName(Context context) { 566 final ContentResolver cr = context.getContentResolver(); 567 final Cursor profile = cr.query(Profile.CONTENT_URI, 568 new String[] {Profile.DISPLAY_NAME}, null, null, null); 569 if (profile == null) return null; 570 571 try { 572 if (!profile.moveToFirst()) { 573 return null; 574 } 575 return profile.getString(0); 576 } finally { 577 profile.close(); 578 } 579 } 580 581 /** Not global warming, it's global change warning. */ 582 public static Dialog buildGlobalChangeWarningDialog(final Context context, int titleResId, 583 final Runnable positiveAction) { 584 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 585 builder.setTitle(titleResId); 586 builder.setMessage(R.string.global_change_warning); 587 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 588 @Override 589 public void onClick(DialogInterface dialog, int which) { 590 positiveAction.run(); 591 } 592 }); 593 builder.setNegativeButton(android.R.string.cancel, null); 594 595 return builder.create(); 596 } 597 598 public static boolean hasMultipleUsers(Context context) { 599 return ((UserManager) context.getSystemService(Context.USER_SERVICE)) 600 .getUsers().size() > 1; 601 } 602} 603