| /*------------------------------------------------------------------------- |
| * drawElements Quality Program Tester Core |
| * ---------------------------------------- |
| * |
| * Copyright 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| *//*! |
| * \file |
| * \brief Android utilities. |
| *//*--------------------------------------------------------------------*/ |
| |
| #include "tcuAndroidUtil.hpp" |
| |
| #include "deSTLUtil.hpp" |
| #include "deMath.h" |
| |
| #include <vector> |
| |
| namespace tcu |
| { |
| namespace Android |
| { |
| |
| using std::string; |
| using std::vector; |
| |
| namespace |
| { |
| |
| class ScopedJNIEnv |
| { |
| public: |
| |
| ScopedJNIEnv (JavaVM* vm); |
| ~ScopedJNIEnv (void); |
| |
| JavaVM* getVM (void) const { return m_vm; } |
| JNIEnv* getEnv (void) const { return m_env; } |
| |
| private: |
| JavaVM* const m_vm; |
| JNIEnv* m_env; |
| bool m_detach; |
| }; |
| |
| ScopedJNIEnv::ScopedJNIEnv (JavaVM* vm) |
| : m_vm (vm) |
| , m_env (DE_NULL) |
| , m_detach (false) |
| { |
| const int getEnvRes = m_vm->GetEnv((void**)&m_env, JNI_VERSION_1_6); |
| |
| if (getEnvRes == JNI_EDETACHED) |
| { |
| if (m_vm->AttachCurrentThread(&m_env, DE_NULL) != JNI_OK) |
| throw std::runtime_error("JNI AttachCurrentThread() failed"); |
| |
| m_detach = true; |
| } |
| else if (getEnvRes != JNI_OK) |
| throw std::runtime_error("JNI GetEnv() failed"); |
| |
| DE_ASSERT(m_env); |
| } |
| |
| ScopedJNIEnv::~ScopedJNIEnv (void) |
| { |
| if (m_detach) |
| m_vm->DetachCurrentThread(); |
| } |
| |
| class LocalRef |
| { |
| public: |
| LocalRef (JNIEnv* env, jobject ref); |
| ~LocalRef (void); |
| |
| jobject operator* (void) const { return m_ref; } |
| operator bool (void) const { return !!m_ref; } |
| |
| private: |
| LocalRef (const LocalRef&); |
| LocalRef& operator= (const LocalRef&); |
| |
| JNIEnv* const m_env; |
| const jobject m_ref; |
| }; |
| |
| LocalRef::LocalRef (JNIEnv* env, jobject ref) |
| : m_env(env) |
| , m_ref(ref) |
| { |
| } |
| |
| LocalRef::~LocalRef (void) |
| { |
| if (m_ref) |
| m_env->DeleteLocalRef(m_ref); |
| } |
| |
| void checkException (JNIEnv* env) |
| { |
| if (env->ExceptionCheck()) |
| { |
| env->ExceptionDescribe(); |
| env->ExceptionClear(); |
| throw std::runtime_error("Got JNI exception"); |
| } |
| } |
| |
| jclass findClass (JNIEnv* env, const char* className) |
| { |
| const jclass cls = env->FindClass(className); |
| |
| checkException(env); |
| TCU_CHECK_INTERNAL(cls); |
| |
| return cls; |
| } |
| |
| jclass getObjectClass (JNIEnv* env, jobject object) |
| { |
| const jclass cls = env->GetObjectClass(object); |
| |
| checkException(env); |
| TCU_CHECK_INTERNAL(cls); |
| |
| return cls; |
| } |
| |
| jmethodID getMethodID (JNIEnv* env, jclass cls, const char* methodName, const char* signature) |
| { |
| const jmethodID id = env->GetMethodID(cls, methodName, signature); |
| |
| checkException(env); |
| TCU_CHECK_INTERNAL(id); |
| |
| return id; |
| } |
| |
| string getStringValue (JNIEnv* env, jstring jniStr) |
| { |
| const char* ptr = env->GetStringUTFChars(jniStr, DE_NULL); |
| const string str = string(ptr); |
| |
| env->ReleaseStringUTFChars(jniStr, ptr); |
| |
| return str; |
| } |
| |
| string getIntentStringExtra (JNIEnv* env, jobject activity, const char* name) |
| { |
| // \todo [2013-05-12 pyry] Clean up references on error. |
| |
| const jclass activityCls = getObjectClass(env, activity); |
| const LocalRef intent (env, env->CallObjectMethod(activity, getMethodID(env, activityCls, "getIntent", "()Landroid/content/Intent;"))); |
| TCU_CHECK_INTERNAL(intent); |
| |
| const LocalRef extraName (env, env->NewStringUTF(name)); |
| const jclass intentCls = getObjectClass(env, *intent); |
| TCU_CHECK_INTERNAL(extraName && intentCls); |
| |
| jvalue getExtraArgs[1]; |
| getExtraArgs[0].l = *extraName; |
| |
| const LocalRef extraStr (env, env->CallObjectMethodA(*intent, getMethodID(env, intentCls, "getStringExtra", "(Ljava/lang/String;)Ljava/lang/String;"), getExtraArgs)); |
| |
| if (extraStr) |
| return getStringValue(env, (jstring)*extraStr); |
| else |
| return string(); |
| } |
| |
| void setRequestedOrientation (JNIEnv* env, jobject activity, ScreenOrientation orientation) |
| { |
| const jclass activityCls = getObjectClass(env, activity); |
| const jmethodID setOrientationId = getMethodID(env, activityCls, "setRequestedOrientation", "(I)V"); |
| |
| env->CallVoidMethod(activity, setOrientationId, (int)orientation); |
| } |
| |
| template<typename Type> |
| const char* getJNITypeStr (void); |
| |
| template<> |
| const char* getJNITypeStr<int> (void) |
| { |
| return "I"; |
| } |
| |
| template<> |
| const char* getJNITypeStr<float> (void) |
| { |
| return "F"; |
| } |
| |
| template<> |
| const char* getJNITypeStr<string> (void) |
| { |
| return "Ljava/lang/String;"; |
| } |
| |
| template<> |
| const char* getJNITypeStr<vector<string> > (void) |
| { |
| return "[Ljava/lang/String;"; |
| } |
| |
| template<typename FieldType> |
| FieldType getStaticFieldValue (JNIEnv* env, jclass cls, jfieldID fieldId); |
| |
| template<> |
| int getStaticFieldValue<int> (JNIEnv* env, jclass cls, jfieldID fieldId) |
| { |
| DE_ASSERT(cls && fieldId); |
| return env->GetStaticIntField(cls, fieldId); |
| } |
| |
| template<> |
| string getStaticFieldValue<string> (JNIEnv* env, jclass cls, jfieldID fieldId) |
| { |
| const jstring jniStr = (jstring)env->GetStaticObjectField(cls, fieldId); |
| |
| if (jniStr) |
| return getStringValue(env, jniStr); |
| else |
| return string(); |
| } |
| |
| template<> |
| vector<string> getStaticFieldValue<vector<string> > (JNIEnv* env, jclass cls, jfieldID fieldId) |
| { |
| const jobjectArray array = (jobjectArray)env->GetStaticObjectField(cls, fieldId); |
| vector<string> result; |
| |
| checkException(env); |
| |
| if (array) |
| { |
| const int numElements = env->GetArrayLength(array); |
| |
| for (int ndx = 0; ndx < numElements; ndx++) |
| { |
| const jstring jniStr = (jstring)env->GetObjectArrayElement(array, ndx); |
| |
| checkException(env); |
| |
| if (jniStr) |
| result.push_back(getStringValue(env, jniStr)); |
| } |
| } |
| |
| return result; |
| } |
| |
| template<typename FieldType> |
| FieldType getStaticField (JNIEnv* env, const char* className, const char* fieldName) |
| { |
| const jclass cls = findClass(env, className); |
| const jfieldID fieldId = env->GetStaticFieldID(cls, fieldName, getJNITypeStr<FieldType>()); |
| |
| checkException(env); |
| |
| if (fieldId) |
| return getStaticFieldValue<FieldType>(env, cls, fieldId); |
| else |
| throw std::runtime_error(string(fieldName) + " not found in " + className); |
| } |
| |
| template<typename FieldType> |
| FieldType getFieldValue (JNIEnv* env, jobject obj, jfieldID fieldId); |
| |
| template<> |
| int getFieldValue<int> (JNIEnv* env, jobject obj, jfieldID fieldId) |
| { |
| DE_ASSERT(obj && fieldId); |
| return env->GetIntField(obj, fieldId); |
| } |
| |
| template<> |
| float getFieldValue<float> (JNIEnv* env, jobject obj, jfieldID fieldId) |
| { |
| DE_ASSERT(obj && fieldId); |
| return env->GetFloatField(obj, fieldId); |
| } |
| |
| template<typename FieldType> |
| FieldType getField (JNIEnv* env, jobject obj, const char* fieldName) |
| { |
| const jclass cls = getObjectClass(env, obj); |
| const jfieldID fieldId = env->GetFieldID(cls, fieldName, getJNITypeStr<FieldType>()); |
| |
| checkException(env); |
| |
| if (fieldId) |
| return getFieldValue<FieldType>(env, obj, fieldId); |
| else |
| throw std::runtime_error(string(fieldName) + " not found in object"); |
| } |
| |
| void describePlatform (JNIEnv* env, std::ostream& dst) |
| { |
| const char* const buildClass = "android/os/Build"; |
| const char* const versionClass = "android/os/Build$VERSION"; |
| |
| static const struct |
| { |
| const char* classPath; |
| const char* className; |
| const char* fieldName; |
| } s_stringFields[] = |
| { |
| { buildClass, "Build", "BOARD" }, |
| { buildClass, "Build", "BRAND" }, |
| { buildClass, "Build", "DEVICE" }, |
| { buildClass, "Build", "DISPLAY" }, |
| { buildClass, "Build", "FINGERPRINT" }, |
| { buildClass, "Build", "HARDWARE" }, |
| { buildClass, "Build", "MANUFACTURER" }, |
| { buildClass, "Build", "MODEL" }, |
| { buildClass, "Build", "PRODUCT" }, |
| { buildClass, "Build", "TAGS" }, |
| { buildClass, "Build", "TYPE" }, |
| { versionClass, "Build.VERSION", "RELEASE" }, |
| }; |
| |
| for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_stringFields); ndx++) |
| dst << s_stringFields[ndx].className << "." << s_stringFields[ndx].fieldName |
| << ": " << getStaticField<string>(env, s_stringFields[ndx].classPath, s_stringFields[ndx].fieldName) |
| << "\n"; |
| |
| dst << "Build.VERSION.SDK_INT: " << getStaticField<int>(env, versionClass, "SDK_INT") << "\n"; |
| |
| { |
| const vector<string> supportedAbis = getStaticField<vector<string> >(env, buildClass, "SUPPORTED_ABIS"); |
| |
| dst << "Build.SUPPORTED_ABIS: "; |
| |
| for (size_t ndx = 0; ndx < supportedAbis.size(); ndx++) |
| dst << (ndx != 0 ? ", " : "") << supportedAbis[ndx]; |
| |
| dst << "\n"; |
| } |
| } |
| |
| vector<string> getSupportedABIs (JNIEnv* env) |
| { |
| return getStaticField<vector<string> >(env, "android/os/Build", "SUPPORTED_ABIS"); |
| } |
| |
| bool supportsAny64BitABI (JNIEnv* env) |
| { |
| const vector<string> supportedAbis = getSupportedABIs(env); |
| const char* known64BitAbis[] = { "arm64-v8a", "x86_64", "mips64" }; |
| |
| for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(known64BitAbis); ++ndx) |
| { |
| if (de::contains(supportedAbis.begin(), supportedAbis.end(), string(known64BitAbis[ndx]))) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool supportsAny64BitABI (ANativeActivity* activity) |
| { |
| const ScopedJNIEnv env(activity->vm); |
| |
| return supportsAny64BitABI(env.getEnv()); |
| } |
| |
| jobject getPackageManager (JNIEnv* env, jobject activity) |
| { |
| const jclass activityCls = getObjectClass(env, activity); |
| const jmethodID getPMID = getMethodID(env, activityCls, "getPackageManager", "()Landroid/content/pm/PackageManager;"); |
| const jobject packageManager = env->CallObjectMethod(activity, getPMID); |
| |
| return packageManager; |
| } |
| |
| bool hasSystemFeature (JNIEnv* env, jobject activity, const char* name) |
| { |
| const LocalRef packageManager (env, getPackageManager(env, activity)); |
| const jclass pmCls = getObjectClass(env, *packageManager); |
| const jmethodID hasFeatureID = getMethodID(env, pmCls, "hasSystemFeature", "(Ljava/lang/String;)Z"); |
| const LocalRef nameStr (env, env->NewStringUTF(name)); |
| jvalue callArgs[1]; |
| |
| callArgs[0].l = *nameStr; |
| |
| return env->CallBooleanMethodA(*packageManager, hasFeatureID, callArgs) == JNI_TRUE; |
| } |
| |
| jobject getWindowManager (JNIEnv* env, jobject activity) |
| { |
| const jclass activityCls = getObjectClass(env, activity); |
| const jmethodID getWMID = getMethodID(env, activityCls, "getWindowManager", "()Landroid/view/WindowManager;"); |
| const jobject windowManager = env->CallObjectMethod(activity, getWMID); |
| |
| return windowManager; |
| } |
| |
| jobject getDefaultDisplay (JNIEnv* env, jobject windowManager) |
| { |
| const jclass wmClass = getObjectClass(env, windowManager); |
| const jmethodID getDisplayID = getMethodID(env, wmClass, "getDefaultDisplay", "()Landroid/view/Display;"); |
| const jobject display = env->CallObjectMethod(windowManager, getDisplayID); |
| |
| return display; |
| } |
| |
| jobject createDisplayMetrics (JNIEnv* env) |
| { |
| const jclass displayMetricsCls = findClass(env, "android/util/DisplayMetrics"); |
| const jmethodID ctorId = getMethodID(env, displayMetricsCls, "<init>", "()V"); |
| |
| return env->NewObject(displayMetricsCls, ctorId); |
| } |
| |
| DisplayMetrics getDisplayMetrics (JNIEnv* env, jobject activity) |
| { |
| const LocalRef windowManager (env, getWindowManager(env, activity)); |
| const LocalRef defaultDisplay (env, getDefaultDisplay(env, *windowManager)); |
| const LocalRef nativeMetrics (env, createDisplayMetrics(env)); |
| const jclass displayCls = getObjectClass(env, *defaultDisplay); |
| const jmethodID getMetricsID = getMethodID(env, displayCls, "getMetrics", "(Landroid/util/DisplayMetrics;)V"); |
| DisplayMetrics metrics; |
| |
| { |
| jvalue callArgs[1]; |
| callArgs[0].l = *nativeMetrics; |
| |
| env->CallVoidMethodA(*defaultDisplay, getMetricsID, callArgs); |
| } |
| |
| metrics.density = getField<float> (env, *nativeMetrics, "density"); |
| metrics.densityDpi = getField<int> (env, *nativeMetrics, "densityDpi"); |
| metrics.scaledDensity = getField<float> (env, *nativeMetrics, "scaledDensity"); |
| metrics.widthPixels = getField<int> (env, *nativeMetrics, "widthPixels"); |
| metrics.heightPixels = getField<int> (env, *nativeMetrics, "heightPixels"); |
| metrics.xdpi = getField<float> (env, *nativeMetrics, "xdpi"); |
| metrics.ydpi = getField<float> (env, *nativeMetrics, "ydpi"); |
| |
| return metrics; |
| } |
| |
| enum ScreenClass |
| { |
| SCREEN_CLASS_WEAR = 0, |
| SCREEN_CLASS_SMALL, |
| SCREEN_CLASS_NORMAL, |
| SCREEN_CLASS_LARGE, |
| SCREEN_CLASS_EXTRA_LARGE, |
| |
| SCREEN_CLASS_LAST |
| }; |
| |
| enum DensityClass |
| { |
| DENSITY_CLASS_LDPI = 120, |
| DENSITY_CLASS_MDPI = 160, |
| DENSITY_CLASS_TVDPI = 213, |
| DENSITY_CLASS_HDPI = 240, |
| DENSITY_CLASS_280DPI = 280, |
| DENSITY_CLASS_XHDPI = 320, |
| DENSITY_CLASS_360DPI = 360, |
| DENSITY_CLASS_400DPI = 400, |
| DENSITY_CLASS_420DPI = 420, |
| DENSITY_CLASS_XXHDPI = 480, |
| DENSITY_CLASS_560DPI = 560, |
| DENSITY_CLASS_XXXHDPI = 640, |
| |
| DENSITY_CLASS_INVALID = -1, |
| }; |
| |
| ScreenClass getScreenClass (const DisplayMetrics& displayMetrics) |
| { |
| static const struct |
| { |
| int minWidthDp; |
| int minHeightDp; |
| ScreenClass screenClass; |
| } s_screenClasses[] = |
| { |
| // Must be ordered from largest to smallest |
| { 960, 720, SCREEN_CLASS_EXTRA_LARGE }, |
| { 640, 480, SCREEN_CLASS_LARGE }, |
| { 480, 320, SCREEN_CLASS_NORMAL }, |
| { 426, 320, SCREEN_CLASS_SMALL }, |
| }; |
| |
| const float dpScale = float(displayMetrics.densityDpi) / 160.f; |
| |
| // \note Assume landscape orientation for comparison |
| const int widthP = de::max(displayMetrics.widthPixels, displayMetrics.heightPixels); |
| const int heightP = de::min(displayMetrics.widthPixels, displayMetrics.heightPixels); |
| |
| const int widthDp = deFloorFloatToInt32(float(widthP) / dpScale); |
| const int heightDp = deFloorFloatToInt32(float(heightP) / dpScale); |
| |
| for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_screenClasses); ++ndx) |
| { |
| if ((s_screenClasses[ndx].minWidthDp <= widthDp) && |
| (s_screenClasses[ndx].minHeightDp <= heightDp)) |
| return s_screenClasses[ndx].screenClass; |
| } |
| |
| return SCREEN_CLASS_WEAR; |
| } |
| |
| bool isValidDensityClass (int dpi) |
| { |
| switch (dpi) |
| { |
| case DENSITY_CLASS_LDPI: |
| case DENSITY_CLASS_MDPI: |
| case DENSITY_CLASS_TVDPI: |
| case DENSITY_CLASS_HDPI: |
| case DENSITY_CLASS_280DPI: |
| case DENSITY_CLASS_XHDPI: |
| case DENSITY_CLASS_360DPI: |
| case DENSITY_CLASS_400DPI: |
| case DENSITY_CLASS_420DPI: |
| case DENSITY_CLASS_XXHDPI: |
| case DENSITY_CLASS_560DPI: |
| case DENSITY_CLASS_XXXHDPI: |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| DensityClass getDensityClass (const DisplayMetrics& displayMetrics) |
| { |
| if (isValidDensityClass(displayMetrics.densityDpi)) |
| return (DensityClass)displayMetrics.densityDpi; |
| else |
| return DENSITY_CLASS_INVALID; |
| } |
| |
| } // anonymous |
| |
| ScreenOrientation mapScreenRotation (ScreenRotation rotation) |
| { |
| switch (rotation) |
| { |
| case SCREENROTATION_UNSPECIFIED: return SCREEN_ORIENTATION_UNSPECIFIED; |
| case SCREENROTATION_0: return SCREEN_ORIENTATION_PORTRAIT; |
| case SCREENROTATION_90: return SCREEN_ORIENTATION_LANDSCAPE; |
| case SCREENROTATION_180: return SCREEN_ORIENTATION_REVERSE_PORTRAIT; |
| case SCREENROTATION_270: return SCREEN_ORIENTATION_REVERSE_LANDSCAPE; |
| default: |
| print("Warning: Unsupported rotation"); |
| return SCREEN_ORIENTATION_PORTRAIT; |
| } |
| } |
| |
| string getIntentStringExtra (ANativeActivity* activity, const char* name) |
| { |
| const ScopedJNIEnv env(activity->vm); |
| |
| return getIntentStringExtra(env.getEnv(), activity->clazz, name); |
| } |
| |
| void setRequestedOrientation (ANativeActivity* activity, ScreenOrientation orientation) |
| { |
| const ScopedJNIEnv env(activity->vm); |
| |
| setRequestedOrientation(env.getEnv(), activity->clazz, orientation); |
| } |
| |
| void describePlatform (ANativeActivity* activity, std::ostream& dst) |
| { |
| const ScopedJNIEnv env(activity->vm); |
| |
| describePlatform(env.getEnv(), dst); |
| } |
| |
| bool hasSystemFeature (ANativeActivity* activity, const char* name) |
| { |
| const ScopedJNIEnv env(activity->vm); |
| |
| return hasSystemFeature(env.getEnv(), activity->clazz, name); |
| } |
| |
| DisplayMetrics getDisplayMetrics (ANativeActivity* activity) |
| { |
| const ScopedJNIEnv env(activity->vm); |
| |
| return getDisplayMetrics(env.getEnv(), activity->clazz); |
| } |
| |
| size_t getCDDRequiredSystemMemory (ANativeActivity* activity) |
| { |
| const DisplayMetrics displayMetrics = getDisplayMetrics(activity); |
| const ScreenClass screenClass = getScreenClass(displayMetrics); |
| const bool isWearDevice = hasSystemFeature(activity, "android.hardware.type.watch"); |
| const bool is64BitDevice = supportsAny64BitABI(activity); |
| const size_t MiB = (size_t)(1<<20); |
| |
| if (!is64BitDevice) |
| TCU_CHECK_INTERNAL(sizeof(void*) != sizeof(deUint64)); |
| |
| if (isWearDevice) |
| { |
| TCU_CHECK_INTERNAL(!is64BitDevice); |
| return 416*MiB; |
| } |
| else |
| { |
| const DensityClass densityClass = getDensityClass(displayMetrics); |
| |
| TCU_CHECK_INTERNAL(de::inRange(screenClass, SCREEN_CLASS_SMALL, SCREEN_CLASS_EXTRA_LARGE)); |
| TCU_CHECK_INTERNAL(densityClass != DENSITY_CLASS_INVALID); |
| |
| static const struct |
| { |
| DensityClass smallNormalScreenDensity; |
| DensityClass largeScreenDensity; |
| DensityClass extraLargeScreenDensity; |
| size_t requiredMem32bit; |
| size_t requiredMem64bit; |
| } s_classes[] = |
| { |
| // Must be ordered from largest to smallest |
| { DENSITY_CLASS_560DPI, DENSITY_CLASS_400DPI, DENSITY_CLASS_XHDPI, 1344*MiB, 1824*MiB }, |
| { DENSITY_CLASS_400DPI, DENSITY_CLASS_XHDPI, DENSITY_CLASS_TVDPI, 896*MiB, 1280*MiB }, |
| { DENSITY_CLASS_XHDPI, DENSITY_CLASS_HDPI, DENSITY_CLASS_MDPI, 512*MiB, 832*MiB }, |
| |
| // \note Last is default, and density values are maximum allowed |
| { DENSITY_CLASS_280DPI, DENSITY_CLASS_MDPI, DENSITY_CLASS_LDPI, 424*MiB, 704*MiB }, |
| }; |
| |
| for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_classes); ++ndx) |
| { |
| const DensityClass minClass = screenClass == SCREEN_CLASS_EXTRA_LARGE ? s_classes[ndx].extraLargeScreenDensity |
| : screenClass == SCREEN_CLASS_LARGE ? s_classes[ndx].largeScreenDensity |
| : /* small/normal */ s_classes[ndx].smallNormalScreenDensity; |
| const size_t reqMem = is64BitDevice ? s_classes[ndx].requiredMem64bit : s_classes[ndx].requiredMem32bit; |
| const bool isLast = ndx == DE_LENGTH_OF_ARRAY(s_classes)-1; |
| |
| if ((isLast && minClass >= densityClass) || (!isLast && minClass <= densityClass)) |
| return reqMem; |
| } |
| |
| TCU_THROW(InternalError, "Invalid combination of density and screen size"); |
| } |
| } |
| |
| } // Android |
| } // tcu |