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.os.Build;
8import android.os.StrictMode;
9import android.util.Log;
10
11import java.io.BufferedReader;
12import java.io.FileReader;
13import java.util.regex.Matcher;
14import java.util.regex.Pattern;
15
16/**
17 * Exposes system related information about the current device.
18 */
19public class SysUtils {
20    // Any device that runs this or an older version of the system cannot be considered 'low-end'
21    private static final int ANDROID_LOW_MEMORY_ANDROID_SDK_THRESHOLD =
22            Build.VERSION_CODES.JELLY_BEAN_MR2;
23
24    // A device reporting strictly more total memory in megabytes cannot be considered 'low-end'.
25    private static final long ANDROID_LOW_MEMORY_DEVICE_THRESHOLD_MB = 512;
26
27    private static final String TAG = "SysUtils";
28
29    private static Boolean sLowEndDevice;
30
31    private SysUtils() { }
32
33    /**
34     * Return the amount of physical memory on this device in kilobytes.
35     * @return Amount of physical memory in kilobytes, or 0 if there was
36     *         an error trying to access the information.
37     */
38    private static int amountOfPhysicalMemoryKB() {
39        // Extract total memory RAM size by parsing /proc/meminfo, note that
40        // this is exactly what the implementation of sysconf(_SC_PHYS_PAGES)
41        // does. However, it can't be called because this method must be
42        // usable before any native code is loaded.
43
44        // An alternative is to use ActivityManager.getMemoryInfo(), but this
45        // requires a valid ActivityManager handle, which can only come from
46        // a valid Context object, which itself cannot be retrieved
47        // during early startup, where this method is called. And making it
48        // an explicit parameter here makes all call paths _much_ more
49        // complicated.
50
51        Pattern pattern = Pattern.compile("^MemTotal:\\s+([0-9]+) kB$");
52        // Synchronously reading files in /proc in the UI thread is safe.
53        StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
54        try {
55            FileReader fileReader = new FileReader("/proc/meminfo");
56            try {
57                BufferedReader reader = new BufferedReader(fileReader);
58                try {
59                    String line;
60                    for (;;) {
61                        line = reader.readLine();
62                        if (line == null) {
63                            Log.w(TAG, "/proc/meminfo lacks a MemTotal entry?");
64                            break;
65                        }
66                        Matcher m = pattern.matcher(line);
67                        if (!m.find()) continue;
68
69                        int totalMemoryKB = Integer.parseInt(m.group(1));
70                        // Sanity check.
71                        if (totalMemoryKB <= 1024) {
72                            Log.w(TAG, "Invalid /proc/meminfo total size in kB: " + m.group(1));
73                            break;
74                        }
75
76                        return totalMemoryKB;
77                    }
78
79                } finally {
80                    reader.close();
81                }
82            } finally {
83                fileReader.close();
84            }
85        } catch (Exception e) {
86            Log.w(TAG, "Cannot get total physical size from /proc/meminfo", e);
87        } finally {
88            StrictMode.setThreadPolicy(oldPolicy);
89        }
90
91        return 0;
92    }
93
94    /**
95     * @return Whether or not this device should be considered a low end device.
96     */
97    @CalledByNative
98    public static boolean isLowEndDevice() {
99        if (sLowEndDevice == null) {
100            sLowEndDevice = detectLowEndDevice();
101        }
102        return sLowEndDevice.booleanValue();
103    }
104
105    private static boolean detectLowEndDevice() {
106        assert CommandLine.isInitialized();
107        if (CommandLine.getInstance().hasSwitch(BaseSwitches.LOW_END_DEVICE_MODE)) {
108            int mode = Integer.parseInt(CommandLine.getInstance().getSwitchValue(
109                    BaseSwitches.LOW_END_DEVICE_MODE));
110            if (mode == 1) return true;
111            if (mode == 0) return false;
112        }
113
114        if (Build.VERSION.SDK_INT <= ANDROID_LOW_MEMORY_ANDROID_SDK_THRESHOLD) {
115            return false;
116        }
117
118        int ramSizeKB = amountOfPhysicalMemoryKB();
119        return (ramSizeKB > 0 && ramSizeKB / 1024 < ANDROID_LOW_MEMORY_DEVICE_THRESHOLD_MB);
120    }
121}
122