1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5package org.chromium.base; 6 7import android.annotation.TargetApi; 8import android.app.Activity; 9import android.app.ActivityManager; 10import android.app.PendingIntent; 11import android.content.ContentResolver; 12import android.content.Context; 13import android.content.Intent; 14import android.content.pm.PackageManager; 15import android.content.res.ColorStateList; 16import android.content.res.Configuration; 17import android.content.res.Resources; 18import android.content.res.Resources.NotFoundException; 19import android.graphics.Bitmap; 20import android.graphics.Color; 21import android.graphics.ColorFilter; 22import android.graphics.Rect; 23import android.graphics.drawable.Drawable; 24import android.os.Build; 25import android.os.PowerManager; 26import android.os.Process; 27import android.os.StatFs; 28import android.os.UserManager; 29import android.provider.Settings; 30import android.view.View; 31import android.view.ViewGroup.MarginLayoutParams; 32import android.view.Window; 33import android.view.WindowManager; 34import android.widget.TextView; 35 36import java.lang.reflect.Method; 37 38/** 39 * Utility class to use new APIs that were added after ICS (API level 14). 40 */ 41@TargetApi(Build.VERSION_CODES.LOLLIPOP) 42public class ApiCompatibilityUtils { 43 private ApiCompatibilityUtils() { 44 } 45 46 /** 47 * Returns true if view's layout direction is right-to-left. 48 * 49 * @param view the View whose layout is being considered 50 */ 51 public static boolean isLayoutRtl(View view) { 52 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 53 return view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; 54 } else { 55 // All layouts are LTR before JB MR1. 56 return false; 57 } 58 } 59 60 /** 61 * @see Configuration#getLayoutDirection() 62 */ 63 public static int getLayoutDirection(Configuration configuration) { 64 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 65 return configuration.getLayoutDirection(); 66 } else { 67 // All layouts are LTR before JB MR1. 68 return View.LAYOUT_DIRECTION_LTR; 69 } 70 } 71 72 /** 73 * @return True if the running version of the Android supports printing. 74 */ 75 public static boolean isPrintingSupported() { 76 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; 77 } 78 79 /** 80 * @return True if the running version of the Android supports elevation. Elevation of a view 81 * determines the visual appearance of its shadow. 82 */ 83 public static boolean isElevationSupported() { 84 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; 85 } 86 87 /** 88 * @see android.view.View#setLayoutDirection(int) 89 */ 90 public static void setLayoutDirection(View view, int layoutDirection) { 91 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 92 view.setLayoutDirection(layoutDirection); 93 } else { 94 // Do nothing. RTL layouts aren't supported before JB MR1. 95 } 96 } 97 98 /** 99 * @see android.view.View#setTextAlignment(int) 100 */ 101 public static void setTextAlignment(View view, int textAlignment) { 102 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 103 view.setTextAlignment(textAlignment); 104 } else { 105 // Do nothing. RTL text isn't supported before JB MR1. 106 } 107 } 108 109 /** 110 * @see android.view.View#setTextDirection(int) 111 */ 112 public static void setTextDirection(View view, int textDirection) { 113 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 114 view.setTextDirection(textDirection); 115 } else { 116 // Do nothing. RTL text isn't supported before JB MR1. 117 } 118 } 119 120 /** 121 * See {@link android.view.View#setLabelFor(int)}. 122 */ 123 public static void setLabelFor(View labelView, int id) { 124 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 125 labelView.setLabelFor(id); 126 } else { 127 // Do nothing. #setLabelFor() isn't supported before JB MR1. 128 } 129 } 130 131 /** 132 * @see android.view.ViewGroup.MarginLayoutParams#setMarginEnd(int) 133 */ 134 public static void setMarginEnd(MarginLayoutParams layoutParams, int end) { 135 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 136 layoutParams.setMarginEnd(end); 137 } else { 138 layoutParams.rightMargin = end; 139 } 140 } 141 142 /** 143 * @see android.view.ViewGroup.MarginLayoutParams#getMarginEnd() 144 */ 145 public static int getMarginEnd(MarginLayoutParams layoutParams) { 146 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 147 return layoutParams.getMarginEnd(); 148 } else { 149 return layoutParams.rightMargin; 150 } 151 } 152 153 /** 154 * @see android.view.ViewGroup.MarginLayoutParams#setMarginStart(int) 155 */ 156 public static void setMarginStart(MarginLayoutParams layoutParams, int start) { 157 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 158 layoutParams.setMarginStart(start); 159 } else { 160 layoutParams.leftMargin = start; 161 } 162 } 163 164 /** 165 * @see android.view.ViewGroup.MarginLayoutParams#getMarginStart() 166 */ 167 public static int getMarginStart(MarginLayoutParams layoutParams) { 168 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 169 return layoutParams.getMarginStart(); 170 } else { 171 return layoutParams.leftMargin; 172 } 173 } 174 175 /** 176 * @see android.view.View#setPaddingRelative(int, int, int, int) 177 */ 178 public static void setPaddingRelative(View view, int start, int top, int end, int bottom) { 179 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 180 view.setPaddingRelative(start, top, end, bottom); 181 } else { 182 // Before JB MR1, all layouts are left-to-right, so start == left, etc. 183 view.setPadding(start, top, end, bottom); 184 } 185 } 186 187 /** 188 * @see android.view.View#getPaddingStart() 189 */ 190 public static int getPaddingStart(View view) { 191 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 192 return view.getPaddingStart(); 193 } else { 194 // Before JB MR1, all layouts are left-to-right, so start == left. 195 return view.getPaddingLeft(); 196 } 197 } 198 199 /** 200 * @see android.view.View#getPaddingEnd() 201 */ 202 public static int getPaddingEnd(View view) { 203 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 204 return view.getPaddingEnd(); 205 } else { 206 // Before JB MR1, all layouts are left-to-right, so end == right. 207 return view.getPaddingRight(); 208 } 209 } 210 211 /** 212 * @see android.widget.TextView#setCompoundDrawablesRelative(Drawable, Drawable, Drawable, 213 * Drawable) 214 */ 215 public static void setCompoundDrawablesRelative(TextView textView, Drawable start, Drawable top, 216 Drawable end, Drawable bottom) { 217 if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) { 218 // On JB MR1, due to a platform bug, setCompoundDrawablesRelative() is a no-op if the 219 // view has ever been measured. As a workaround, use setCompoundDrawables() directly. 220 // See: http://crbug.com/368196 and http://crbug.com/361709 221 boolean isRtl = isLayoutRtl(textView); 222 textView.setCompoundDrawables(isRtl ? end : start, top, isRtl ? start : end, bottom); 223 } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1) { 224 textView.setCompoundDrawablesRelative(start, top, end, bottom); 225 } else { 226 textView.setCompoundDrawables(start, top, end, bottom); 227 } 228 } 229 230 /** 231 * @see android.widget.TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable, 232 * Drawable, Drawable, Drawable) 233 */ 234 public static void setCompoundDrawablesRelativeWithIntrinsicBounds(TextView textView, 235 Drawable start, Drawable top, Drawable end, Drawable bottom) { 236 if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) { 237 // Work around the platform bug described in setCompoundDrawablesRelative() above. 238 boolean isRtl = isLayoutRtl(textView); 239 textView.setCompoundDrawablesWithIntrinsicBounds(isRtl ? end : start, top, 240 isRtl ? start : end, bottom); 241 } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1) { 242 textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom); 243 } else { 244 textView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom); 245 } 246 } 247 248 /** 249 * @see android.widget.TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(int, int, int, 250 * int) 251 */ 252 public static void setCompoundDrawablesRelativeWithIntrinsicBounds(TextView textView, 253 int start, int top, int end, int bottom) { 254 if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) { 255 // Work around the platform bug described in setCompoundDrawablesRelative() above. 256 boolean isRtl = isLayoutRtl(textView); 257 textView.setCompoundDrawablesWithIntrinsicBounds(isRtl ? end : start, top, 258 isRtl ? start : end, bottom); 259 } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1) { 260 textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom); 261 } else { 262 textView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom); 263 } 264 } 265 266 // These methods have a new name, and the old name is deprecated. 267 268 /** 269 * @see android.app.PendingIntent#getCreatorPackage() 270 */ 271 @SuppressWarnings("deprecation") 272 public static String getCreatorPackage(PendingIntent intent) { 273 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 274 return intent.getCreatorPackage(); 275 } else { 276 return intent.getTargetPackage(); 277 } 278 } 279 280 /** 281 * @see android.provider.Settings.Global#DEVICE_PROVISIONED 282 */ 283 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) 284 public static boolean isDeviceProvisioned(Context context) { 285 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) return true; 286 if (context == null) return true; 287 if (context.getContentResolver() == null) return true; 288 return Settings.Global.getInt( 289 context.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0; 290 } 291 292 /** 293 * @see android.app.Activity#finishAndRemoveTask() 294 */ 295 public static void finishAndRemoveTask(Activity activity) { 296 if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) { 297 activity.finishAndRemoveTask(); 298 } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) { 299 // crbug.com/395772 : Fallback for Activity.finishAndRemoveTask() failing. 300 new FinishAndRemoveTaskWithRetry(activity).run(); 301 } else { 302 activity.finish(); 303 } 304 } 305 306 /** 307 * Set elevation if supported. 308 */ 309 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 310 public static boolean setElevation(View view, float elevationValue) { 311 if (!isElevationSupported()) return false; 312 313 view.setElevation(elevationValue); 314 return true; 315 } 316 317 private static class FinishAndRemoveTaskWithRetry implements Runnable { 318 private static final long RETRY_DELAY_MS = 500; 319 private static final long MAX_TRY_COUNT = 3; 320 private final Activity mActivity; 321 private int mTryCount; 322 323 FinishAndRemoveTaskWithRetry(Activity activity) { 324 mActivity = activity; 325 } 326 327 @Override 328 public void run() { 329 mActivity.finishAndRemoveTask(); 330 mTryCount++; 331 if (!mActivity.isFinishing()) { 332 if (mTryCount < MAX_TRY_COUNT) { 333 ThreadUtils.postOnUiThreadDelayed(this, RETRY_DELAY_MS); 334 } else { 335 mActivity.finish(); 336 } 337 } 338 } 339 } 340 341 /** 342 * @return Whether the screen of the device is interactive. 343 */ 344 @SuppressWarnings("deprecation") 345 public static boolean isInteractive(Context context) { 346 PowerManager manager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 347 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) { 348 return manager.isInteractive(); 349 } else { 350 return manager.isScreenOn(); 351 } 352 } 353 354 @SuppressWarnings("deprecation") 355 public static int getActivityNewDocumentFlag() { 356 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 357 return Intent.FLAG_ACTIVITY_NEW_DOCUMENT; 358 } else { 359 return Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET; 360 } 361 } 362 363 /** 364 * @see android.provider.Settings.Secure#SKIP_FIRST_USE_HINTS 365 */ 366 public static boolean shouldSkipFirstUseHints(ContentResolver contentResolver) { 367 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 368 return Settings.Secure.getInt( 369 contentResolver, Settings.Secure.SKIP_FIRST_USE_HINTS, 0) != 0; 370 } else { 371 return false; 372 } 373 } 374 375 /** 376 * @param activity Activity that should get the task description update. 377 * @param title Title of the activity. 378 * @param icon Icon of the activity. 379 * @param color Color of the activity. It must be a fully opaque color. 380 */ 381 public static void setTaskDescription(Activity activity, String title, Bitmap icon, int color) { 382 // TaskDescription requires an opaque color. 383 assert Color.alpha(color) == 255; 384 385 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 386 ActivityManager.TaskDescription description = 387 new ActivityManager.TaskDescription(title, icon, color); 388 activity.setTaskDescription(description); 389 } 390 } 391 392 /** 393 * @see android.view.Window#setStatusBarColor(int color). 394 */ 395 public static void setStatusBarColor(Window window, int statusBarColor) { 396 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 397 // If both system bars are black, we can remove these from our layout, 398 // removing or shrinking the SurfaceFlinger overlay required for our views. 399 if (statusBarColor == Color.BLACK && window.getNavigationBarColor() == Color.BLACK) { 400 window.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 401 } else { 402 window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 403 } 404 window.setStatusBarColor(statusBarColor); 405 } 406 } 407 408 /** 409 * @see android.content.res.Resources#getDrawable(int id). 410 */ 411 @SuppressWarnings("deprecation") 412 public static Drawable getDrawable(Resources res, int id) throws NotFoundException { 413 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 414 return res.getDrawable(id, null); 415 } else { 416 return res.getDrawable(id); 417 } 418 } 419 420 /** 421 * @see android.content.res.Resources#getDrawableForDensity(int id, int density). 422 */ 423 @SuppressWarnings("deprecation") 424 public static Drawable getDrawableForDensity(Resources res, int id, int density) { 425 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 426 return res.getDrawableForDensity(id, density, null); 427 } else { 428 return res.getDrawableForDensity(id, density); 429 } 430 } 431 432 /** 433 * @see android.app.Activity#finishAfterTransition(). 434 */ 435 public static void finishAfterTransition(Activity activity) { 436 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 437 activity.finishAfterTransition(); 438 } else { 439 activity.finish(); 440 } 441 } 442 443 /** 444 * @see android.content.pm.PackageManager#getUserBadgedIcon(Drawable, android.os.UserHandle). 445 */ 446 public static Drawable getUserBadgedIcon(Context context, int id) { 447 Drawable drawable = getDrawable(context.getResources(), id); 448 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 449 PackageManager packageManager = context.getPackageManager(); 450 drawable = packageManager.getUserBadgedIcon(drawable, Process.myUserHandle()); 451 } 452 return drawable; 453 } 454 455 /** 456 * @see android.content.pm.PackageManager#getUserBadgedDrawableForDensity(Drawable drawable, 457 * UserHandle user, Rect badgeLocation, int badgeDensity). 458 */ 459 public static Drawable getUserBadgedDrawableForDensity( 460 Context context, Drawable drawable, Rect badgeLocation, int density) { 461 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 462 PackageManager packageManager = context.getPackageManager(); 463 return packageManager.getUserBadgedDrawableForDensity( 464 drawable, Process.myUserHandle(), badgeLocation, density); 465 } 466 return drawable; 467 } 468 469 /** 470 * @see android.content.res.Resources#getColor(int id). 471 */ 472 @SuppressWarnings("deprecation") 473 public static int getColor(Resources res, int id) throws NotFoundException { 474 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 475 return res.getColor(id, null); 476 } else { 477 return res.getColor(id); 478 } 479 } 480 481 /** 482 * @see android.graphics.drawable.Drawable#getColorFilter(). 483 */ 484 @SuppressWarnings("NewApi") 485 public static ColorFilter getColorFilter(Drawable drawable) { 486 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 487 return drawable.getColorFilter(); 488 } else { 489 return null; 490 } 491 } 492 493 /** 494 * @see android.content.res.Resources#getColorStateList(int id). 495 */ 496 @SuppressWarnings("deprecation") 497 public static ColorStateList getColorStateList(Resources res, int id) throws NotFoundException { 498 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 499 return res.getColorStateList(id, null); 500 } else { 501 return res.getColorStateList(id); 502 } 503 } 504 505 /** 506 * @see android.widget.TextView#setTextAppearance(int id). 507 */ 508 @SuppressWarnings("deprecation") 509 public static void setTextAppearance(TextView view, int id) { 510 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 511 view.setTextAppearance(id); 512 } else { 513 view.setTextAppearance(view.getContext(), id); 514 } 515 } 516 517 /** 518 * See {@link android.os.StatFs#getBlockCount()}. 519 */ 520 @SuppressWarnings("deprecation") 521 public static long getBlockCount(StatFs statFs) { 522 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 523 return statFs.getBlockCountLong(); 524 } else { 525 return statFs.getBlockCount(); 526 } 527 } 528 529 /** 530 * See {@link android.os.StatFs#getBlockSize()}. 531 */ 532 @SuppressWarnings("deprecation") 533 public static long getBlockSize(StatFs statFs) { 534 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 535 return statFs.getBlockSizeLong(); 536 } else { 537 return statFs.getBlockSize(); 538 } 539 } 540 541 /** 542 * @param context The Android context, used to retrieve the UserManager system service. 543 * @return Whether the device is running in demo mode. 544 */ 545 public static boolean isDemoUser(Context context) { 546 // UserManager#isDemoUser() is only available in Android versions greater than N. 547 if (!BuildInfo.isGreaterThanN()) return false; 548 549 try { 550 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE); 551 Method isDemoUserMethod = UserManager.class.getMethod("isDemoUser"); 552 boolean isDemoUser = (boolean) isDemoUserMethod.invoke(userManager); 553 return isDemoUser; 554 } catch (RuntimeException e) { 555 // Ignore to avoid crashing on startup. 556 } catch (Exception e) { 557 // Ignore. 558 } 559 560 return false; 561 } 562} 563