/* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.device.yearclass;
import android.content.Context;
import java.util.ArrayList;
import java.util.Collections;
public class YearClass {
// Year definitions
public static final int CLASS_UNKNOWN = -1;
public static final int CLASS_2008 = 2008;
public static final int CLASS_2009 = 2009;
public static final int CLASS_2010 = 2010;
public static final int CLASS_2011 = 2011;
public static final int CLASS_2012 = 2012;
public static final int CLASS_2013 = 2013;
public static final int CLASS_2014 = 2014;
public static final int CLASS_2015 = 2015;
private static final long MB = 1024 * 1024;
private static final int MHZ_IN_KHZ = 1000;
private volatile static Integer mYearCategory;
/**
* Entry Point of YearClass. Extracts YearClass variable with memoizing.
* Example usage:
* <p>
* <pre>
* int yearClass = YearClass.get(context);
* </pre>
*/
public static int get(Context c) {
if (mYearCategory == null) {
synchronized(YearClass.class) {
if (mYearCategory == null) {
mYearCategory = categorizeByYear2016Method(c);
}
}
}
return mYearCategory;
}
private static void conditionallyAdd(ArrayList<Integer> list, int value) {
if (value != CLASS_UNKNOWN) {
list.add(value);
}
}
/**
* This formulation of year class smooths out the distribution of devices in the field
* in early 2016 so that the buckets are a bit more even in size and performance metrics
* (specifically app startup time, scrolling perf, animations) are more uniform within
* the buckets than with the 2014 calculations.
*/
private static int categorizeByYear2016Method(Context c) {
long totalRam = DeviceInfo.getTotalMemory(c);
if (totalRam == DeviceInfo.DEVICEINFO_UNKNOWN) {
return categorizeByYear2014Method(c);
}
if (totalRam <= 768 * MB) {
return DeviceInfo.getNumberOfCPUCores() <= 1 ? CLASS_2009 : CLASS_2010;
}
if (totalRam <= 1024 * MB) {
return DeviceInfo.getCPUMaxFreqKHz() < 1300 * MHZ_IN_KHZ ? CLASS_2011 : CLASS_2012;
}
if (totalRam <= 1536 * MB) {
return DeviceInfo.getCPUMaxFreqKHz() < 1800 * MHZ_IN_KHZ ? CLASS_2012 : CLASS_2013;
}
if (totalRam <= 2048 * MB) {
return CLASS_2013;
}
return totalRam <= 3 * 1024 * MB ? CLASS_2014 : CLASS_2015;
}
/**
* Calculates the "best-in-class year" of the device. This represents the top-end or flagship
* devices of that year, not the actual release year of the phone. For example, the Galaxy Duos
* S was released in 2012, but its specs are very similar to the Galaxy S that was released in
* 2010 as a then top-of-the-line phone, so it is a 2010 device.
*
* @return The year when this device would have been considered top-of-the-line.
*/
private static int categorizeByYear2014Method(Context c) {
ArrayList<Integer> componentYears = new ArrayList<Integer>();
conditionallyAdd(componentYears, getNumCoresYear());
conditionallyAdd(componentYears, getClockSpeedYear());
conditionallyAdd(componentYears, getRamYear(c));
if (componentYears.isEmpty())
return CLASS_UNKNOWN;
Collections.sort(componentYears);
if ((componentYears.size() & 0x01) == 1) { // Odd number; pluck the median.
return componentYears.get(componentYears.size() / 2);
} else { // Even number. Average the two "center" values.
int baseIndex = componentYears.size() / 2 - 1;
// There's an implicit rounding down in here; 2011.5 becomes 2011.
return componentYears.get(baseIndex) +
(componentYears.get(baseIndex + 1) - componentYears.get(baseIndex)) / 2;
}
}
/**
* Calculates the year class by the number of processor cores the phone has.
* Evaluations are based off the table below:
* <table border="1">
* <thead>
* <tr><th width="50%">Amount</th><th>Year</th></tr>
* <thead>
* <tbody>
* <tr><td>>4 or More</td><td>2012</td></tr>
* <tr><td>2 or 3</td><td>2011</td></tr>
* <tr><td>1</td><td>2008</td></tr>
* </tbody>
* </table>
*
* @return the year in which top-of-the-line phones had the same number of processors as this phone.
*/
private static int getNumCoresYear() {
int cores = DeviceInfo.getNumberOfCPUCores();
if (cores < 1) return CLASS_UNKNOWN;
if (cores == 1) return CLASS_2008;
if (cores <= 3) return CLASS_2011;
return CLASS_2012;
}
/**
* Calculates the year class by the clock speed of the cores in the phone.
* Evaluations are based off the table below:
* <table border="1">
* <thead>
* <tr><th width="50%">Amount</th><th>Year</th></tr>
* <thead>
* <tbody>
* <tr><td>>2GHz</td><td>2014</td></tr>
* <tr><td><=2GHz</td><td>2013</td></tr>
* <tr><td><=1.5GHz</td><td>2012</td></tr>
* <tr><td><=1.2GHz</td><td>2011</td></tr>
* <tr><td><=1GHz</td><td>2010</td></tr>
* <tr><td><=600MHz</td><td>2009</td></tr>
* <tr><td><=528MHz</td><td>2008</td></tr>
* </tbody>
* </table>
*
* @return the year in which top-of-the-line phones had the same clock speed.
*/
private static int getClockSpeedYear() {
long clockSpeedKHz = DeviceInfo.getCPUMaxFreqKHz();
if (clockSpeedKHz == DeviceInfo.DEVICEINFO_UNKNOWN) return CLASS_UNKNOWN;
// These cut-offs include 20MHz of "slop" because my "1.5GHz" Galaxy S3 reports
// its clock speed as 1512000. So we add a little slop to keep things nominally correct.
if (clockSpeedKHz <= 528 * MHZ_IN_KHZ) return CLASS_2008;
if (clockSpeedKHz <= 620 * MHZ_IN_KHZ) return CLASS_2009;
if (clockSpeedKHz <= 1020 * MHZ_IN_KHZ) return CLASS_2010;
if (clockSpeedKHz <= 1220 * MHZ_IN_KHZ) return CLASS_2011;
if (clockSpeedKHz <= 1520 * MHZ_IN_KHZ) return CLASS_2012;
if (clockSpeedKHz <= 2020 * MHZ_IN_KHZ) return CLASS_2013;
return CLASS_2014;
}
/**
* Calculates the year class by the amount of RAM the phone has.
* Evaluations are based off the table below:
* <table border="1">
* <thead>
* <tr><th width="50%">Amount</th><th>Year</th></tr>
* <thead>
* <tbody>
* <tr><td>>2GB</td><td>2014</td></tr>
* <tr><td><=2GB</td><td>2013</td></tr>
* <tr><td><=1.5GB</td><td>2012</td></tr>
* <tr><td><=1GB</td><td>2011</td></tr>
* <tr><td><=512MB</td><td>2010</td></tr>
* <tr><td><=256MB</td><td>2009</td></tr>
* <tr><td><=128MB</td><td>2008</td></tr>
* </tbody>
* </table>
*
* @return the year in which top-of-the-line phones had the same amount of RAM as this phone.
*/
private static int getRamYear(Context c) {
long totalRam = DeviceInfo.getTotalMemory(c);
if (totalRam <= 0) return CLASS_UNKNOWN;
if (totalRam <= 192 * MB) return CLASS_2008;
if (totalRam <= 290 * MB) return CLASS_2009;
if (totalRam <= 512 * MB) return CLASS_2010;
if (totalRam <= 1024 * MB) return CLASS_2011;
if (totalRam <= 1536 * MB) return CLASS_2012;
if (totalRam <= 2048 * MB) return CLASS_2013;
return CLASS_2014;
}
}