michael@0: /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.util; michael@0: michael@0: import android.content.Context; michael@0: import android.content.pm.PackageManager; michael@0: import android.content.res.Configuration; michael@0: import android.os.Build; michael@0: import android.util.Log; michael@0: import android.view.ViewConfiguration; michael@0: michael@0: import java.io.FileInputStream; michael@0: import java.io.FileNotFoundException; michael@0: import java.io.IOException; michael@0: import java.util.regex.Matcher; michael@0: import java.util.regex.Pattern; michael@0: michael@0: public final class HardwareUtils { michael@0: private static final String LOGTAG = "GeckoHardwareUtils"; michael@0: michael@0: // Minimum memory threshold for a device to be considered michael@0: // a low memory platform (see isLowMemoryPlatform). This value michael@0: // has be in sync with Gecko's equivalent threshold (defined in michael@0: // xpcom/base/nsMemoryImpl.cpp) and should only be used in cases michael@0: // where we can't depend on Gecko to be up and running e.g. show/hide michael@0: // reading list capabilities in HomePager. michael@0: private static final int LOW_MEMORY_THRESHOLD_MB = 384; michael@0: michael@0: // Number of bytes of /proc/meminfo to read in one go. michael@0: private static final int MEMINFO_BUFFER_SIZE_BYTES = 256; michael@0: michael@0: private static volatile int sTotalRAM = -1; michael@0: michael@0: private static Context sContext; michael@0: michael@0: private static Boolean sIsLargeTablet; michael@0: private static Boolean sIsSmallTablet; michael@0: private static Boolean sIsTelevision; michael@0: private static Boolean sHasMenuButton; michael@0: michael@0: private HardwareUtils() { michael@0: } michael@0: michael@0: public static void init(Context context) { michael@0: if (sContext != null) { michael@0: Log.w(LOGTAG, "HardwareUtils.init called twice!"); michael@0: } michael@0: sContext = context; michael@0: } michael@0: michael@0: public static boolean isTablet() { michael@0: return isLargeTablet() || isSmallTablet(); michael@0: } michael@0: michael@0: public static boolean isLargeTablet() { michael@0: if (sIsLargeTablet == null) { michael@0: int screenLayout = sContext.getResources().getConfiguration().screenLayout; michael@0: sIsLargeTablet = (Build.VERSION.SDK_INT >= 11 && michael@0: ((screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_XLARGE)); michael@0: } michael@0: return sIsLargeTablet; michael@0: } michael@0: michael@0: public static boolean isSmallTablet() { michael@0: if (sIsSmallTablet == null) { michael@0: int screenLayout = sContext.getResources().getConfiguration().screenLayout; michael@0: sIsSmallTablet = (Build.VERSION.SDK_INT >= 11 && michael@0: ((screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_LARGE)); michael@0: } michael@0: return sIsSmallTablet; michael@0: } michael@0: michael@0: public static boolean isTelevision() { michael@0: if (sIsTelevision == null) { michael@0: sIsTelevision = sContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION); michael@0: } michael@0: return sIsTelevision; michael@0: } michael@0: michael@0: public static boolean hasMenuButton() { michael@0: if (sHasMenuButton == null) { michael@0: sHasMenuButton = Boolean.TRUE; michael@0: if (Build.VERSION.SDK_INT >= 11) { michael@0: sHasMenuButton = Boolean.FALSE; michael@0: } michael@0: if (Build.VERSION.SDK_INT >= 14) { michael@0: sHasMenuButton = ViewConfiguration.get(sContext).hasPermanentMenuKey(); michael@0: } michael@0: } michael@0: return sHasMenuButton; michael@0: } michael@0: michael@0: /** michael@0: * Helper functions used to extract key/value data from /proc/meminfo michael@0: * Pulled from: michael@0: * http://androidxref.com/4.2_r1/xref/frameworks/base/core/java/com/android/internal/util/MemInfoReader.java michael@0: */ michael@0: michael@0: private static boolean matchMemText(byte[] buffer, int index, int bufferLength, byte[] text) { michael@0: final int N = text.length; michael@0: if ((index + N) >= bufferLength) { michael@0: return false; michael@0: } michael@0: for (int i = 0; i < N; i++) { michael@0: if (buffer[index + i] != text[i]) { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Parses a line like: michael@0: * michael@0: * MemTotal: 1605324 kB michael@0: * michael@0: * into 1605324. michael@0: * michael@0: * @return the first uninterrupted sequence of digits following the michael@0: * specified index, parsed as an integer value in KB. michael@0: */ michael@0: private static int extractMemValue(byte[] buffer, int offset, int length) { michael@0: if (offset >= length) { michael@0: return 0; michael@0: } michael@0: michael@0: while (offset < length && buffer[offset] != '\n') { michael@0: if (buffer[offset] >= '0' && buffer[offset] <= '9') { michael@0: int start = offset++; michael@0: while (offset < length && michael@0: buffer[offset] >= '0' && michael@0: buffer[offset] <= '9') { michael@0: ++offset; michael@0: } michael@0: return Integer.parseInt(new String(buffer, start, offset - start), 10); michael@0: } michael@0: ++offset; michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: /** michael@0: * Fetch the total memory of the device in MB by parsing /proc/meminfo. michael@0: * michael@0: * Of course, Android doesn't have a neat and tidy way to find total michael@0: * RAM, so we do it by parsing /proc/meminfo. michael@0: * michael@0: * @return 0 if a problem occurred, or memory size in MB. michael@0: */ michael@0: public static int getMemSize() { michael@0: if (sTotalRAM >= 0) { michael@0: return sTotalRAM; michael@0: } michael@0: michael@0: // This is the string "MemTotal" that we're searching for in the buffer. michael@0: final byte[] MEMTOTAL = {'M', 'e', 'm', 'T', 'o', 't', 'a', 'l'}; michael@0: try { michael@0: final byte[] buffer = new byte[MEMINFO_BUFFER_SIZE_BYTES]; michael@0: final FileInputStream is = new FileInputStream("/proc/meminfo"); michael@0: try { michael@0: final int length = is.read(buffer); michael@0: michael@0: for (int i = 0; i < length; i++) { michael@0: if (matchMemText(buffer, i, length, MEMTOTAL)) { michael@0: i += 8; michael@0: sTotalRAM = extractMemValue(buffer, i, length) / 1024; michael@0: Log.d(LOGTAG, "System memory: " + sTotalRAM + "MB."); michael@0: return sTotalRAM; michael@0: } michael@0: } michael@0: } finally { michael@0: is.close(); michael@0: } michael@0: michael@0: Log.w(LOGTAG, "Did not find MemTotal line in /proc/meminfo."); michael@0: return sTotalRAM = 0; michael@0: } catch (FileNotFoundException f) { michael@0: return sTotalRAM = 0; michael@0: } catch (IOException e) { michael@0: return sTotalRAM = 0; michael@0: } michael@0: } michael@0: michael@0: public static boolean isLowMemoryPlatform() { michael@0: final int memSize = getMemSize(); michael@0: michael@0: // Fallback to false if we fail to read meminfo michael@0: // for some reason. michael@0: if (memSize == 0) { michael@0: Log.w(LOGTAG, "Could not compute system memory. Falling back to isLowMemoryPlatform = false."); michael@0: return false; michael@0: } michael@0: michael@0: return memSize < LOW_MEMORY_THRESHOLD_MB; michael@0: } michael@0: }