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