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