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 5 package org.chromium.base; 6 7 import android.annotation.TargetApi; 8 import android.app.ActivityManager; 9 import android.content.Context; 10 import android.content.pm.PackageManager; 11 import android.os.Build; 12 import android.os.Environment; 13 import android.os.StatFs; 14 import android.os.StrictMode; 15 import android.util.Log; 16 17 import androidx.annotation.VisibleForTesting; 18 19 import org.chromium.base.annotations.CalledByNative; 20 import org.chromium.base.annotations.JNINamespace; 21 import org.chromium.base.annotations.MainDex; 22 import org.chromium.base.annotations.NativeMethods; 23 import org.chromium.base.metrics.RecordHistogram; 24 25 import java.io.BufferedReader; 26 import java.io.FileReader; 27 import java.util.regex.Matcher; 28 import java.util.regex.Pattern; 29 30 /** 31 * Exposes system related information about the current device. 32 */ 33 @JNINamespace("base::android") 34 @MainDex 35 public class SysUtils { 36 // A device reporting strictly more total memory in megabytes cannot be considered 'low-end'. 37 private static final int ANDROID_LOW_MEMORY_DEVICE_THRESHOLD_MB = 512; 38 private static final int ANDROID_O_LOW_MEMORY_DEVICE_THRESHOLD_MB = 1024; 39 private static final int BYTES_PER_GIGABYTE = 1024 * 1024 * 1024; 40 41 // A device reporting more disk capacity in gigabytes than this is considered high end. 42 private static final long HIGH_END_DEVICE_DISK_CAPACITY_GB = 24; 43 44 private static final String TAG = "SysUtils"; 45 46 private static Boolean sLowEndDevice; 47 private static Integer sAmountOfPhysicalMemoryKB; 48 49 private static Boolean sHighEndDiskDevice; 50 SysUtils()51 private SysUtils() { } 52 53 /** 54 * Return the amount of physical memory on this device in kilobytes. 55 * @return Amount of physical memory in kilobytes, or 0 if there was 56 * an error trying to access the information. 57 */ detectAmountOfPhysicalMemoryKB()58 private static int detectAmountOfPhysicalMemoryKB() { 59 // Extract total memory RAM size by parsing /proc/meminfo, note that 60 // this is exactly what the implementation of sysconf(_SC_PHYS_PAGES) 61 // does. However, it can't be called because this method must be 62 // usable before any native code is loaded. 63 64 // An alternative is to use ActivityManager.getMemoryInfo(), but this 65 // requires a valid ActivityManager handle, which can only come from 66 // a valid Context object, which itself cannot be retrieved 67 // during early startup, where this method is called. And making it 68 // an explicit parameter here makes all call paths _much_ more 69 // complicated. 70 71 Pattern pattern = Pattern.compile("^MemTotal:\\s+([0-9]+) kB$"); 72 // Synchronously reading files in /proc in the UI thread is safe. 73 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); 74 try { 75 FileReader fileReader = new FileReader("/proc/meminfo"); 76 try { 77 BufferedReader reader = new BufferedReader(fileReader); 78 try { 79 String line; 80 for (;;) { 81 line = reader.readLine(); 82 if (line == null) { 83 Log.w(TAG, "/proc/meminfo lacks a MemTotal entry?"); 84 break; 85 } 86 Matcher m = pattern.matcher(line); 87 if (!m.find()) continue; 88 89 int totalMemoryKB = Integer.parseInt(m.group(1)); 90 // Sanity check. 91 if (totalMemoryKB <= 1024) { 92 Log.w(TAG, "Invalid /proc/meminfo total size in kB: " + m.group(1)); 93 break; 94 } 95 96 return totalMemoryKB; 97 } 98 99 } finally { 100 reader.close(); 101 } 102 } finally { 103 fileReader.close(); 104 } 105 } catch (Exception e) { 106 Log.w(TAG, "Cannot get total physical size from /proc/meminfo", e); 107 } finally { 108 StrictMode.setThreadPolicy(oldPolicy); 109 } 110 111 return 0; 112 } 113 114 /** 115 * @return Whether or not this device should be considered a low end device. 116 */ 117 @CalledByNative isLowEndDevice()118 public static boolean isLowEndDevice() { 119 if (sLowEndDevice == null) { 120 sLowEndDevice = detectLowEndDevice(); 121 } 122 return sLowEndDevice.booleanValue(); 123 } 124 125 /** 126 * @return Whether or not this device should be considered a low end device. 127 */ amountOfPhysicalMemoryKB()128 public static int amountOfPhysicalMemoryKB() { 129 if (sAmountOfPhysicalMemoryKB == null) { 130 sAmountOfPhysicalMemoryKB = detectAmountOfPhysicalMemoryKB(); 131 } 132 return sAmountOfPhysicalMemoryKB.intValue(); 133 } 134 135 /** 136 * @return Whether or not the system has low available memory. 137 */ 138 @CalledByNative isCurrentlyLowMemory()139 public static boolean isCurrentlyLowMemory() { 140 ActivityManager am = 141 (ActivityManager) ContextUtils.getApplicationContext().getSystemService( 142 Context.ACTIVITY_SERVICE); 143 ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo(); 144 am.getMemoryInfo(info); 145 return info.lowMemory; 146 } 147 148 /** 149 * Resets the cached value, if any. 150 */ 151 @VisibleForTesting resetForTesting()152 public static void resetForTesting() { 153 sLowEndDevice = null; 154 sAmountOfPhysicalMemoryKB = null; 155 } 156 hasCamera(final Context context)157 public static boolean hasCamera(final Context context) { 158 final PackageManager pm = context.getPackageManager(); 159 // JellyBean support. 160 boolean hasCamera = pm.hasSystemFeature(PackageManager.FEATURE_CAMERA); 161 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 162 hasCamera |= pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY); 163 } 164 return hasCamera; 165 } 166 167 @TargetApi(Build.VERSION_CODES.KITKAT) detectLowEndDevice()168 private static boolean detectLowEndDevice() { 169 assert CommandLine.isInitialized(); 170 if (CommandLine.getInstance().hasSwitch(BaseSwitches.ENABLE_LOW_END_DEVICE_MODE)) { 171 return true; 172 } 173 if (CommandLine.getInstance().hasSwitch(BaseSwitches.DISABLE_LOW_END_DEVICE_MODE)) { 174 return false; 175 } 176 177 // If this logic changes, update the comments above base::SysUtils::IsLowEndDevice. 178 sAmountOfPhysicalMemoryKB = detectAmountOfPhysicalMemoryKB(); 179 boolean isLowEnd = true; 180 if (sAmountOfPhysicalMemoryKB <= 0) { 181 isLowEnd = false; 182 } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 183 isLowEnd = sAmountOfPhysicalMemoryKB / 1024 <= ANDROID_O_LOW_MEMORY_DEVICE_THRESHOLD_MB; 184 } else { 185 isLowEnd = sAmountOfPhysicalMemoryKB / 1024 <= ANDROID_LOW_MEMORY_DEVICE_THRESHOLD_MB; 186 } 187 188 // For evaluation purposes check whether our computation agrees with Android API value. 189 Context appContext = ContextUtils.getApplicationContext(); 190 boolean isLowRam = false; 191 if (appContext != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 192 isLowRam = ((ActivityManager) ContextUtils.getApplicationContext().getSystemService( 193 Context.ACTIVITY_SERVICE)) 194 .isLowRamDevice(); 195 } 196 RecordHistogram.recordBooleanHistogram( 197 "Android.SysUtilsLowEndMatches", isLowEnd == isLowRam); 198 199 return isLowEnd; 200 } 201 202 /** 203 * Creates a new trace event to log the number of minor / major page faults, if tracing is 204 * enabled. 205 */ logPageFaultCountToTracing()206 public static void logPageFaultCountToTracing() { 207 SysUtilsJni.get().logPageFaultCountToTracing(); 208 } 209 210 /** 211 * @return Whether or not this device should be considered a high end device from a disk 212 * capacity point of view. 213 */ isHighEndDiskDevice()214 public static boolean isHighEndDiskDevice() { 215 if (sHighEndDiskDevice == null) { 216 sHighEndDiskDevice = detectHighEndDiskDevice(); 217 } 218 return sHighEndDiskDevice.booleanValue(); 219 } 220 detectHighEndDiskDevice()221 private static boolean detectHighEndDiskDevice() { 222 try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) { 223 StatFs dataStats = new StatFs(Environment.getDataDirectory().getAbsolutePath()); 224 long totalGBytes = dataStats.getTotalBytes() / BYTES_PER_GIGABYTE; 225 return totalGBytes >= HIGH_END_DEVICE_DISK_CAPACITY_GB; 226 } catch (IllegalArgumentException e) { 227 Log.v(TAG, "Cannot get disk data capacity", e); 228 } 229 return false; 230 } 231 232 @VisibleForTesting setAmountOfPhysicalMemoryKBForTesting(int physicalMemoryKB)233 public static void setAmountOfPhysicalMemoryKBForTesting(int physicalMemoryKB) { 234 sAmountOfPhysicalMemoryKB = physicalMemoryKB; 235 } 236 237 /** 238 * @return Whether this device is running Android Go. This is assumed when we're running Android 239 * O or later and we're on a low-end device. 240 */ isAndroidGo()241 public static boolean isAndroidGo() { 242 return isLowEndDevice() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; 243 } 244 245 @NativeMethods 246 interface Natives { logPageFaultCountToTracing()247 void logPageFaultCountToTracing(); 248 } 249 } 250