1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 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 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, 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. 15 */ 16 package com.android.settings.fuelgauge; 17 18 import android.app.AppOpsManager; 19 import android.content.Context; 20 import android.content.Intent; 21 import android.content.IntentFilter; 22 import android.content.pm.ApplicationInfo; 23 import android.content.pm.PackageInfo; 24 import android.content.pm.PackageManager; 25 import android.content.pm.ResolveInfo; 26 import android.os.BatteryStats; 27 import android.os.Bundle; 28 import android.os.Build; 29 import android.os.Process; 30 import android.os.SystemClock; 31 import android.os.UserHandle; 32 import android.os.UserManager; 33 import android.support.annotation.IntDef; 34 import android.support.annotation.Nullable; 35 import android.support.annotation.StringRes; 36 import android.support.annotation.VisibleForTesting; 37 import android.support.annotation.WorkerThread; 38 import android.text.TextUtils; 39 import android.text.format.DateUtils; 40 import android.util.Log; 41 import android.util.SparseLongArray; 42 43 import com.android.internal.os.BatterySipper; 44 import com.android.internal.os.BatteryStatsHelper; 45 import com.android.internal.util.ArrayUtils; 46 import com.android.settings.R; 47 import com.android.settings.fuelgauge.anomaly.Anomaly; 48 import com.android.settings.fuelgauge.batterytip.AnomalyInfo; 49 import com.android.settings.fuelgauge.batterytip.StatsManagerConfig; 50 import com.android.settings.overlay.FeatureFactory; 51 52 import com.android.settingslib.fuelgauge.PowerWhitelistBackend; 53 import com.android.settingslib.utils.PowerUtil; 54 55 import java.lang.annotation.Retention; 56 import java.lang.annotation.RetentionPolicy; 57 import java.util.Collections; 58 import java.util.Comparator; 59 import java.util.List; 60 61 /** 62 * Utils for battery operation 63 */ 64 public class BatteryUtils { 65 public static final int UID_NULL = -1; 66 public static final int SDK_NULL = -1; 67 68 @Retention(RetentionPolicy.SOURCE) 69 @IntDef({StatusType.SCREEN_USAGE, 70 StatusType.FOREGROUND, 71 StatusType.BACKGROUND, 72 StatusType.ALL 73 }) 74 public @interface StatusType { 75 int SCREEN_USAGE = 0; 76 int FOREGROUND = 1; 77 int BACKGROUND = 2; 78 int ALL = 3; 79 } 80 81 private static final String TAG = "BatteryUtils"; 82 83 private static final int MIN_POWER_THRESHOLD_MILLI_AMP = 5; 84 85 private static final int SECONDS_IN_HOUR = 60 * 60; 86 private static BatteryUtils sInstance; 87 private PackageManager mPackageManager; 88 89 private AppOpsManager mAppOpsManager; 90 private Context mContext; 91 @VisibleForTesting 92 PowerUsageFeatureProvider mPowerUsageFeatureProvider; 93 94 public static BatteryUtils getInstance(Context context) { 95 if (sInstance == null || sInstance.isDataCorrupted()) { 96 sInstance = new BatteryUtils(context); 97 } 98 return sInstance; 99 } 100 101 @VisibleForTesting 102 BatteryUtils(Context context) { 103 mContext = context.getApplicationContext(); 104 mPackageManager = context.getPackageManager(); 105 mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); 106 mPowerUsageFeatureProvider = FeatureFactory.getFactory( 107 context).getPowerUsageFeatureProvider(context); 108 } 109 110 public long getProcessTimeMs(@StatusType int type, @Nullable BatteryStats.Uid uid, 111 int which) { 112 if (uid == null) { 113 return 0; 114 } 115 116 switch (type) { 117 case StatusType.SCREEN_USAGE: 118 return getScreenUsageTimeMs(uid, which); 119 case StatusType.FOREGROUND: 120 return getProcessForegroundTimeMs(uid, which); 121 case StatusType.BACKGROUND: 122 return getProcessBackgroundTimeMs(uid, which); 123 case StatusType.ALL: 124 return getProcessForegroundTimeMs(uid, which) 125 + getProcessBackgroundTimeMs(uid, which); 126 } 127 return 0; 128 } 129 130 private long getScreenUsageTimeMs(BatteryStats.Uid uid, int which, long rawRealTimeUs) { 131 final int foregroundTypes[] = {BatteryStats.Uid.PROCESS_STATE_TOP}; 132 Log.v(TAG, "package: " + mPackageManager.getNameForUid(uid.getUid())); 133 134 long timeUs = 0; 135 for (int type : foregroundTypes) { 136 final long localTime = uid.getProcessStateTime(type, rawRealTimeUs, which); 137 Log.v(TAG, "type: " + type + " time(us): " + localTime); 138 timeUs += localTime; 139 } 140 Log.v(TAG, "foreground time(us): " + timeUs); 141 142 // Return the min value of STATE_TOP time and foreground activity time, since both of these 143 // time have some errors 144 return PowerUtil.convertUsToMs( 145 Math.min(timeUs, getForegroundActivityTotalTimeUs(uid, rawRealTimeUs))); 146 } 147 148 private long getScreenUsageTimeMs(BatteryStats.Uid uid, int which) { 149 final long rawRealTimeUs = PowerUtil.convertMsToUs(SystemClock.elapsedRealtime()); 150 return getScreenUsageTimeMs(uid, which, rawRealTimeUs); 151 } 152 153 private long getProcessBackgroundTimeMs(BatteryStats.Uid uid, int which) { 154 final long rawRealTimeUs = PowerUtil.convertMsToUs(SystemClock.elapsedRealtime()); 155 final long timeUs = uid.getProcessStateTime( 156 BatteryStats.Uid.PROCESS_STATE_BACKGROUND, rawRealTimeUs, which); 157 158 Log.v(TAG, "package: " + mPackageManager.getNameForUid(uid.getUid())); 159 Log.v(TAG, "background time(us): " + timeUs); 160 return PowerUtil.convertUsToMs(timeUs); 161 } 162 163 private long getProcessForegroundTimeMs(BatteryStats.Uid uid, int which) { 164 final long rawRealTimeUs = PowerUtil.convertMsToUs(SystemClock.elapsedRealtime()); 165 return getScreenUsageTimeMs(uid, which, rawRealTimeUs) 166 + PowerUtil.convertUsToMs(getForegroundServiceTotalTimeUs(uid, rawRealTimeUs)); 167 } 168 169 /** 170 * Remove the {@link BatterySipper} that we should hide and smear the screen usage based on 171 * foreground activity time. 172 * 173 * @param sippers sipper list that need to check and remove 174 * @return the total power of the hidden items of {@link BatterySipper} 175 * for proportional smearing 176 */ 177 public double removeHiddenBatterySippers(List<BatterySipper> sippers) { 178 double proportionalSmearPowerMah = 0; 179 BatterySipper screenSipper = null; 180 for (int i = sippers.size() - 1; i >= 0; i--) { 181 final BatterySipper sipper = sippers.get(i); 182 if (shouldHideSipper(sipper)) { 183 sippers.remove(i); 184 if (sipper.drainType != BatterySipper.DrainType.OVERCOUNTED 185 && sipper.drainType != BatterySipper.DrainType.SCREEN 186 && sipper.drainType != BatterySipper.DrainType.UNACCOUNTED 187 && sipper.drainType != BatterySipper.DrainType.BLUETOOTH 188 && sipper.drainType != BatterySipper.DrainType.WIFI 189 && sipper.drainType != BatterySipper.DrainType.IDLE) { 190 // Don't add it if it is overcounted, unaccounted, wifi, bluetooth, or screen 191 proportionalSmearPowerMah += sipper.totalPowerMah; 192 } 193 } 194 195 if (sipper.drainType == BatterySipper.DrainType.SCREEN) { 196 screenSipper = sipper; 197 } 198 } 199 200 smearScreenBatterySipper(sippers, screenSipper); 201 202 return proportionalSmearPowerMah; 203 } 204 205 /** 206 * Smear the screen on power usage among {@code sippers}, based on ratio of foreground activity 207 * time. 208 */ 209 @VisibleForTesting 210 void smearScreenBatterySipper(List<BatterySipper> sippers, BatterySipper screenSipper) { 211 long totalActivityTimeMs = 0; 212 final SparseLongArray activityTimeArray = new SparseLongArray(); 213 for (int i = 0, size = sippers.size(); i < size; i++) { 214 final BatteryStats.Uid uid = sippers.get(i).uidObj; 215 if (uid != null) { 216 final long timeMs = getProcessTimeMs(StatusType.SCREEN_USAGE, uid, 217 BatteryStats.STATS_SINCE_CHARGED); 218 activityTimeArray.put(uid.getUid(), timeMs); 219 totalActivityTimeMs += timeMs; 220 } 221 } 222 223 if (totalActivityTimeMs >= 10 * DateUtils.MINUTE_IN_MILLIS) { 224 if (screenSipper == null) { 225 Log.e(TAG, "screen sipper is null even when app screen time is not zero"); 226 return; 227 } 228 229 final double screenPowerMah = screenSipper.totalPowerMah; 230 for (int i = 0, size = sippers.size(); i < size; i++) { 231 final BatterySipper sipper = sippers.get(i); 232 sipper.totalPowerMah += screenPowerMah * activityTimeArray.get(sipper.getUid(), 0) 233 / totalActivityTimeMs; 234 } 235 } 236 } 237 238 /** 239 * Check whether we should hide the battery sipper. 240 */ 241 public boolean shouldHideSipper(BatterySipper sipper) { 242 final BatterySipper.DrainType drainType = sipper.drainType; 243 244 return drainType == BatterySipper.DrainType.IDLE 245 || drainType == BatterySipper.DrainType.CELL 246 || drainType == BatterySipper.DrainType.SCREEN 247 || drainType == BatterySipper.DrainType.UNACCOUNTED 248 || drainType == BatterySipper.DrainType.OVERCOUNTED 249 || drainType == BatterySipper.DrainType.BLUETOOTH 250 || drainType == BatterySipper.DrainType.WIFI 251 || (sipper.totalPowerMah * SECONDS_IN_HOUR) < MIN_POWER_THRESHOLD_MILLI_AMP 252 || mPowerUsageFeatureProvider.isTypeService(sipper) 253 || mPowerUsageFeatureProvider.isTypeSystem(sipper); 254 } 255 256 /** 257 * Calculate the power usage percentage for an app 258 * 259 * @param powerUsageMah power used by the app 260 * @param totalPowerMah total power used in the system 261 * @param hiddenPowerMah power used by no-actionable app that we want to hide, i.e. Screen, 262 * Android OS. 263 * @param dischargeAmount The discharge amount calculated by {@link BatteryStats} 264 * @return A percentage value scaled by {@paramref dischargeAmount} 265 * @see BatteryStats#getDischargeAmount(int) 266 */ 267 public double calculateBatteryPercent(double powerUsageMah, double totalPowerMah, 268 double hiddenPowerMah, int dischargeAmount) { 269 if (totalPowerMah == 0) { 270 return 0; 271 } 272 273 return (powerUsageMah / (totalPowerMah - hiddenPowerMah)) * dischargeAmount; 274 } 275 276 /** 277 * Calculate the whole running time in the state {@code statsType} 278 * 279 * @param batteryStatsHelper utility class that contains the data 280 * @param statsType state that we want to calculate the time for 281 * @return the running time in millis 282 */ 283 public long calculateRunningTimeBasedOnStatsType(BatteryStatsHelper batteryStatsHelper, 284 int statsType) { 285 final long elapsedRealtimeUs = PowerUtil.convertMsToUs( 286 SystemClock.elapsedRealtime()); 287 // Return the battery time (millisecond) on status mStatsType 288 return PowerUtil.convertUsToMs( 289 batteryStatsHelper.getStats().computeBatteryRealtime(elapsedRealtimeUs, statsType)); 290 291 } 292 293 /** 294 * Find the package name for a {@link android.os.BatteryStats.Uid} 295 * 296 * @param uid id to get the package name 297 * @return the package name. If there are multiple packages related to 298 * given id, return the first one. Or return null if there are no known 299 * packages with the given id 300 * @see PackageManager#getPackagesForUid(int) 301 */ 302 public String getPackageName(int uid) { 303 final String[] packageNames = mPackageManager.getPackagesForUid(uid); 304 305 return ArrayUtils.isEmpty(packageNames) ? null : packageNames[0]; 306 } 307 308 /** 309 * Find the targetSdkVersion for package with name {@code packageName} 310 * 311 * @return the targetSdkVersion, or {@link #SDK_NULL} if {@code packageName} doesn't exist 312 */ 313 public int getTargetSdkVersion(final String packageName) { 314 try { 315 ApplicationInfo info = mPackageManager.getApplicationInfo(packageName, 316 PackageManager.GET_META_DATA); 317 318 return info.targetSdkVersion; 319 } catch (PackageManager.NameNotFoundException e) { 320 Log.e(TAG, "Cannot find package: " + packageName, e); 321 } 322 323 return SDK_NULL; 324 } 325 326 /** 327 * Check whether background restriction is enabled 328 */ 329 public boolean isBackgroundRestrictionEnabled(final int targetSdkVersion, final int uid, 330 final String packageName) { 331 if (targetSdkVersion >= Build.VERSION_CODES.O) { 332 return true; 333 } 334 final int mode = mAppOpsManager 335 .checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, uid, packageName); 336 return mode == AppOpsManager.MODE_IGNORED || mode == AppOpsManager.MODE_ERRORED; 337 } 338 339 /** 340 * Sort the {@code usageList} based on {@link BatterySipper#totalPowerMah} 341 */ 342 public void sortUsageList(List<BatterySipper> usageList) { 343 Collections.sort(usageList, new Comparator<BatterySipper>() { 344 @Override 345 public int compare(BatterySipper a, BatterySipper b) { 346 return Double.compare(b.totalPowerMah, a.totalPowerMah); 347 } 348 }); 349 } 350 351 /** 352 * Calculate the time since last full charge, including the device off time 353 * 354 * @param batteryStatsHelper utility class that contains the data 355 * @param currentTimeMs current wall time 356 * @return time in millis 357 */ 358 public long calculateLastFullChargeTime(BatteryStatsHelper batteryStatsHelper, 359 long currentTimeMs) { 360 return currentTimeMs - batteryStatsHelper.getStats().getStartClockTime(); 361 362 } 363 364 /** 365 * Calculate the screen usage time since last full charge. 366 * 367 * @param batteryStatsHelper utility class that contains the screen usage data 368 * @return time in millis 369 */ 370 public long calculateScreenUsageTime(BatteryStatsHelper batteryStatsHelper) { 371 final BatterySipper sipper = findBatterySipperByType( 372 batteryStatsHelper.getUsageList(), BatterySipper.DrainType.SCREEN); 373 return sipper != null ? sipper.usageTimeMs : 0; 374 } 375 376 public static void logRuntime(String tag, String message, long startTime) { 377 Log.d(tag, message + ": " + (System.currentTimeMillis() - startTime) + "ms"); 378 } 379 380 /** 381 * Find package uid from package name 382 * 383 * @param packageName used to find the uid 384 * @return uid for packageName, or {@link #UID_NULL} if exception happens or 385 * {@code packageName} is null 386 */ 387 public int getPackageUid(String packageName) { 388 try { 389 return packageName == null ? UID_NULL : mPackageManager.getPackageUid(packageName, 390 PackageManager.GET_META_DATA); 391 } catch (PackageManager.NameNotFoundException e) { 392 return UID_NULL; 393 } 394 } 395 396 @StringRes 397 public int getSummaryResIdFromAnomalyType(@Anomaly.AnomalyType int type) { 398 switch (type) { 399 case Anomaly.AnomalyType.WAKE_LOCK: 400 return R.string.battery_abnormal_wakelock_summary; 401 case Anomaly.AnomalyType.WAKEUP_ALARM: 402 return R.string.battery_abnormal_wakeup_alarm_summary; 403 case Anomaly.AnomalyType.BLUETOOTH_SCAN: 404 return R.string.battery_abnormal_location_summary; 405 default: 406 throw new IllegalArgumentException("Incorrect anomaly type: " + type); 407 } 408 } 409 410 public void setForceAppStandby(int uid, String packageName, 411 int mode) { 412 final boolean isPreOApp = isPreOApp(packageName); 413 if (isPreOApp) { 414 // Control whether app could run in the background if it is pre O app 415 mAppOpsManager.setMode(AppOpsManager.OP_RUN_IN_BACKGROUND, uid, packageName, mode); 416 } 417 // Control whether app could run jobs in the background 418 mAppOpsManager.setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName, mode); 419 } 420 421 public boolean isForceAppStandbyEnabled(int uid, String packageName) { 422 return mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, 423 packageName) == AppOpsManager.MODE_IGNORED; 424 } 425 426 public void initBatteryStatsHelper(BatteryStatsHelper statsHelper, Bundle bundle, 427 UserManager userManager) { 428 statsHelper.create(bundle); 429 statsHelper.clearStats(); 430 statsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, userManager.getUserProfiles()); 431 } 432 433 @WorkerThread 434 public BatteryInfo getBatteryInfo(final BatteryStatsHelper statsHelper, final String tag) { 435 final long startTime = System.currentTimeMillis(); 436 437 // Stuff we always need to get BatteryInfo 438 final Intent batteryBroadcast = mContext.registerReceiver(null, 439 new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); 440 final long elapsedRealtimeUs = PowerUtil.convertMsToUs( 441 SystemClock.elapsedRealtime()); 442 final BatteryStats stats = statsHelper.getStats(); 443 BatteryInfo batteryInfo; 444 445 final Estimate estimate; 446 // Get enhanced prediction if available 447 if (mPowerUsageFeatureProvider != null && 448 mPowerUsageFeatureProvider.isEnhancedBatteryPredictionEnabled(mContext)) { 449 estimate = mPowerUsageFeatureProvider.getEnhancedBatteryPrediction(mContext); 450 } else { 451 estimate = new Estimate( 452 PowerUtil.convertUsToMs(stats.computeBatteryTimeRemaining(elapsedRealtimeUs)), 453 false /* isBasedOnUsage */, 454 Estimate.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN); 455 } 456 457 BatteryUtils.logRuntime(tag, "BatteryInfoLoader post query", startTime); 458 batteryInfo = BatteryInfo.getBatteryInfo(mContext, batteryBroadcast, stats, 459 estimate, elapsedRealtimeUs, false /* shortString */); 460 BatteryUtils.logRuntime(tag, "BatteryInfoLoader.loadInBackground", startTime); 461 462 return batteryInfo; 463 } 464 465 /** 466 * Find the {@link BatterySipper} with the corresponding {@link BatterySipper.DrainType} 467 */ 468 public BatterySipper findBatterySipperByType(List<BatterySipper> usageList, 469 BatterySipper.DrainType type) { 470 for (int i = 0, size = usageList.size(); i < size; i++) { 471 final BatterySipper sipper = usageList.get(i); 472 if (sipper.drainType == type) { 473 return sipper; 474 } 475 } 476 return null; 477 } 478 479 private boolean isDataCorrupted() { 480 return mPackageManager == null || mAppOpsManager == null; 481 } 482 483 @VisibleForTesting 484 long getForegroundActivityTotalTimeUs(BatteryStats.Uid uid, long rawRealtimeUs) { 485 final BatteryStats.Timer timer = uid.getForegroundActivityTimer(); 486 if (timer != null) { 487 return timer.getTotalTimeLocked(rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED); 488 } 489 490 return 0; 491 } 492 493 @VisibleForTesting 494 long getForegroundServiceTotalTimeUs(BatteryStats.Uid uid, long rawRealtimeUs) { 495 final BatteryStats.Timer timer = uid.getForegroundServiceTimer(); 496 if (timer != null) { 497 return timer.getTotalTimeLocked(rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED); 498 } 499 500 return 0; 501 } 502 503 public boolean isPreOApp(final String packageName) { 504 try { 505 ApplicationInfo info = mPackageManager.getApplicationInfo(packageName, 506 PackageManager.GET_META_DATA); 507 508 return info.targetSdkVersion < Build.VERSION_CODES.O; 509 } catch (PackageManager.NameNotFoundException e) { 510 Log.e(TAG, "Cannot find package: " + packageName, e); 511 } 512 513 return false; 514 } 515 516 public boolean isPreOApp(final String[] packageNames) { 517 if (ArrayUtils.isEmpty(packageNames)) { 518 return false; 519 } 520 521 for (String packageName : packageNames) { 522 if (isPreOApp(packageName)) { 523 return true; 524 } 525 } 526 527 return false; 528 } 529 530 /** 531 * Return {@code true} if we should hide anomaly app represented by {@code uid} 532 */ 533 public boolean shouldHideAnomaly(PowerWhitelistBackend powerWhitelistBackend, int uid, 534 AnomalyInfo anomalyInfo) { 535 final String[] packageNames = mPackageManager.getPackagesForUid(uid); 536 if (ArrayUtils.isEmpty(packageNames)) { 537 // Don't show it if app has been uninstalled 538 return true; 539 } 540 541 return isSystemUid(uid) || powerWhitelistBackend.isWhitelisted(packageNames) 542 || (isSystemApp(mPackageManager, packageNames) && !hasLauncherEntry(packageNames)) 543 || (isExcessiveBackgroundAnomaly(anomalyInfo) && !isPreOApp(packageNames)); 544 } 545 546 private boolean isExcessiveBackgroundAnomaly(AnomalyInfo anomalyInfo) { 547 return anomalyInfo.anomalyType 548 == StatsManagerConfig.AnomalyType.EXCESSIVE_BACKGROUND_SERVICE; 549 } 550 551 private boolean isSystemUid(int uid) { 552 final int appUid = UserHandle.getAppId(uid); 553 return appUid >= Process.ROOT_UID && appUid < Process.FIRST_APPLICATION_UID; 554 } 555 556 private boolean isSystemApp(PackageManager packageManager, String[] packageNames) { 557 for (String packageName : packageNames) { 558 try { 559 final ApplicationInfo info = packageManager.getApplicationInfo(packageName, 560 0 /* flags */); 561 if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 562 return true; 563 } 564 } catch (PackageManager.NameNotFoundException e) { 565 Log.e(TAG, "Package not found: " + packageName, e); 566 } 567 } 568 569 return false; 570 } 571 572 private boolean hasLauncherEntry(String[] packageNames) { 573 final Intent launchIntent = new Intent(Intent.ACTION_MAIN, null); 574 launchIntent.addCategory(Intent.CATEGORY_LAUNCHER); 575 576 // If we do not specify MATCH_DIRECT_BOOT_AWARE or 577 // MATCH_DIRECT_BOOT_UNAWARE, system will derive and update the flags 578 // according to the user's lock state. When the user is locked, 579 // components 580 // with ComponentInfo#directBootAware == false will be filtered. We should 581 // explicitly include both direct boot aware and unaware components here. 582 final List<ResolveInfo> resolveInfos = mPackageManager.queryIntentActivities(launchIntent, 583 PackageManager.MATCH_DISABLED_COMPONENTS 584 | PackageManager.MATCH_DIRECT_BOOT_AWARE 585 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE 586 | PackageManager.MATCH_SYSTEM_ONLY); 587 for (int i = 0, size = resolveInfos.size(); i < size; i++) { 588 final ResolveInfo resolveInfo = resolveInfos.get(i); 589 if (ArrayUtils.contains(packageNames, resolveInfo.activityInfo.packageName)) { 590 return true; 591 } 592 } 593 594 return false; 595 } 596 597 /** 598 * Return version number of an app represented by {@code packageName}, and return -1 if not 599 * found. 600 */ 601 public long getAppLongVersionCode(String packageName) { 602 try { 603 final PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName, 604 0 /* flags */); 605 return packageInfo.getLongVersionCode(); 606 } catch (PackageManager.NameNotFoundException e) { 607 Log.e(TAG, "Cannot find package: " + packageName, e); 608 } 609 610 return -1L; 611 } 612 } 613 614