content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you 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.
   */
  package org.apache.weex;
  
  import static android.content.Context.MODE_PRIVATE;
  
  import android.annotation.SuppressLint;
  import android.app.Application;
  import android.content.Context;
  import android.content.SharedPreferences;
  import android.content.pm.ApplicationInfo;
  import android.content.pm.PackageInfo;
  import android.content.pm.PackageManager;
  import android.graphics.Typeface;
  import android.os.Build;
  import android.os.Environment;
  import android.support.annotation.RestrictTo;
  import android.support.annotation.RestrictTo.Scope;
  import android.telephony.TelephonyManager;
  import android.text.TextUtils;
  import android.system.Os;
  import dalvik.system.PathClassLoader;
  import java.io.BufferedReader;
  import java.io.File;
  import java.io.FileDescriptor;
  import java.io.FileReader;
  import java.io.IOException;
  import java.lang.reflect.Field;
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Method;
  import java.util.HashMap;
  import java.util.Locale;
  import java.util.Map;
  import java.util.concurrent.ConcurrentHashMap;
  import org.apache.weex.common.WXConfig;
  import org.apache.weex.utils.FontDO;
  import org.apache.weex.utils.LogLevel;
  import org.apache.weex.utils.TypefaceUtil;
  import org.apache.weex.utils.WXFileUtils;
  import org.apache.weex.utils.WXLogUtils;
  import org.apache.weex.utils.WXSoInstallMgrSdk;
  import org.apache.weex.utils.WXUtils;
  import org.apache.weex.utils.WXViewUtils;
  
  public class WXEnvironment {
  
    public static final String OS = "android";
    public static String SYS_VERSION = android.os.Build.VERSION.RELEASE;
    static{
      if(SYS_VERSION != null && SYS_VERSION.toUpperCase(Locale.ROOT).equals("P")){
          SYS_VERSION = "9.0.0";
      }
      if(SYS_VERSION != null && SYS_VERSION.toUpperCase(Locale.ROOT).equals("Q")){
         SYS_VERSION = "10.0.0";
      }
    }
    public static final String SYS_MODEL = android.os.Build.MODEL;
    public static final String EAGLE = "eagle";
    public static final String ENVIRONMENT = "environment";
    public static final String WEEX_CURRENT_KEY = "wx_current_url";
    /*********************
     * Global config
     ***************************/
  
    public static String JS_LIB_SDK_VERSION = BuildConfig.buildJavascriptFrameworkVersion;
  
    public static String WXSDK_VERSION = BuildConfig.buildVersion;
    public static Application sApplication;
    public static final String DEV_Id = getDevId();
    @Deprecated
    public static int sDefaultWidth = 750;
    public volatile static boolean JsFrameworkInit = false;
  
    public static final String SETTING_EXCLUDE_X86SUPPORT = "env_exclude_x86";
  
    public static boolean SETTING_FORCE_VERTICAL_SCREEN = false;
  
    /**
     * auto adjust device width for when screen size change.
     * */
    public static boolean AUTO_ADJUST_ENV_DEVICE_WIDTH = true;
  
    public static boolean AUTO_UPDATE_APPLICATION_SCREEN_SIZE = true;
  
    @RestrictTo(Scope.LIBRARY)
    public static volatile boolean sUseRunTimeApi;
  
    /**
     * Debug model
     */
    public static boolean sDebugMode = false;
    public static final boolean sForceEnableDevTool = true;
    public static String sDebugWsUrl = "";
    public static boolean sDebugServerConnectable = false;
    public static boolean sRemoteDebugMode = false;
    public static String sRemoteDebugProxyUrl = "";
    public static boolean sDebugNetworkEventReporterEnable = false;//debugtool network switch
    public static long sJSLibInitTime = 0;
  
    public static long sSDKInitStart = 0;// init start timestamp
    public static long sSDKInitInvokeTime = 0;//time cost to invoke init method
    public static long sSDKInitExecuteTime = 0;//time cost to execute init job
    /** from init to sdk-ready **/
    public static long sSDKInitTime =0;
  
    public static long sJSFMStartListenerTime=0;
  
    public static volatile boolean isWsFixMode = true;
  
    /**
     * component and modules ready
     * */
    public static long sComponentsAndModulesReadyTime = 0;
  
    public static boolean sInAliWeex = false;
  
    public static LogLevel sLogLevel = LogLevel.DEBUG;
    private static boolean isApkDebug = true;
    public static boolean isPerf = false;
    private static boolean sDebugFlagInit = false;
  
    private static boolean openDebugLog = true;
  
    private static String sGlobalFontFamily;
  
    public static final String CORE_SO_NAME = "weexcore";
    public static final String CORE_JSS_SO_NAME = "weexjss";
    public static final String CORE_JSB_SO_NAME = "weexjsb";
    public static final String CORE_JST_SO_NAME = "weexjst";
  
    @RestrictTo(Scope.LIBRARY)
    public static String CORE_JSC_SO_NAME = BuildConfig.JSInterpolatorName;
    public static  String CORE_JSS_SO_PATH = null;
  
    private static String CORE_JSS_ICU_PATH = null;
  
    private static String CORE_JSC_SO_PATH = null;
  
    public static String CORE_JSB_SO_PATH = null;
  
    private static String COPY_SO_DES_DIR = null;
  
    private static String LIB_LD_PATH = null;
  
    private static Map<String, String> options = new ConcurrentHashMap<>();
    static {
      options.put(WXConfig.os, OS);
      options.put(WXConfig.osName, OS);
    }
  
  
    public static synchronized WXDefaultSettings getWXDefaultSettings() {
      if (mWXDefaultSettings == null && getApplication() != null) {
        mWXDefaultSettings = new WXDefaultSettings(getApplication());
      }
      return mWXDefaultSettings;
    }
  
    public static synchronized String getDefaultSettingValue(String key, String defaultValue) {
      WXDefaultSettings wxDefaultSettings = getWXDefaultSettings();
      if (wxDefaultSettings == null || TextUtils.isEmpty(key)) {
        return defaultValue;
      }
      return wxDefaultSettings.getValue(key, defaultValue);
    }
  
    public static synchronized void writeDefaultSettingsValue(String key, String value) {
      WXDefaultSettings wxDefaultSettings = getWXDefaultSettings();
      if (wxDefaultSettings == null
              || TextUtils.isEmpty(key)
              || TextUtils.isEmpty(value)) {
        return;
      }
      wxDefaultSettings.saveValue(key, value);
    }
  
    private static WXDefaultSettings mWXDefaultSettings;
  
    /**
     * dynamic
     */
    public static boolean sDynamicMode = false;
    public static String sDynamicUrl = "";
  
    /**
     * Fetch system information.
     * @return map contains system information.
     */
    public static Map<String, String> getConfig() {
      Map<String, String> configs = new HashMap<>();
      configs.put(WXConfig.os, OS);
      configs.put(WXConfig.appVersion, getAppVersionName());
      configs.put(WXConfig.cacheDir, getAppCacheFile());
      configs.put(WXConfig.devId, DEV_Id);
      configs.put(WXConfig.sysVersion, SYS_VERSION);
      configs.put(WXConfig.sysModel, SYS_MODEL);
      configs.put(WXConfig.weexVersion, String.valueOf(WXSDK_VERSION));
  
      try {
        configs.put(WXConfig.layoutDirection, isLayoutDirectionRTL() ? "rtl" : "ltr");
      } catch (Exception e) {
        configs.put(WXConfig.layoutDirection, "ltr");
      }
  
      try {
        if (isApkDebugable()) {
          addCustomOptions(WXConfig.debugMode, "true");
        }
        addCustomOptions(WXConfig.scale, Float.toString(sApplication.getResources().getDisplayMetrics().density));
        addCustomOptions(WXConfig.androidStatusBarHeight, Float.toString(WXViewUtils.getStatusBarHeight(sApplication)));
      }catch (NullPointerException e){
        //There is little chance of NullPointerException as sApplication may be null.
        WXLogUtils.e("WXEnvironment scale Exception: ", e);
      }
      configs.putAll(getCustomOptions());
      if(configs.get(WXConfig.appName)==null && sApplication!=null){
        configs.put(WXConfig.appName, sApplication.getPackageName());
      }
      return configs;
    }
  
    /**
     * Get the version of the current app.
     */
    public static String getAppVersionName() {
      String versionName = "";
      PackageManager manager;
      PackageInfo info = null;
      try {
        manager = sApplication.getPackageManager();
        info = manager.getPackageInfo(sApplication.getPackageName(), 0);
        versionName = info.versionName;
      } catch (Exception e) {
        WXLogUtils.e("WXEnvironment getAppVersionName Exception: ", e);
      }
      return versionName;
    }
  
    /**
     *
     * @return string cache file
     */
    private static String getAppCacheFile() {
      String cache = "";
      try {
        cache = sApplication.getApplicationContext().getCacheDir().getPath();
      } catch (Exception e) {
        WXLogUtils.e("WXEnvironment getAppCacheFile Exception: ", e);
      }
      return cache;
    }
  
  
    /**
     * Use {@link #addCustomOptions(String, String)} to add custom options.
     * Use {@link #getCustomOptions(String)} to get custom options
     * @return
     */
    @Deprecated
    public static Map<String, String> getCustomOptions() {
      return options;
    }
  
    public static void addCustomOptions(String key, String value) {
      options.put(key, value);
    }
  
    public static String getCustomOptions(String key){
      return options.get(key);
    }
  
  
    @SuppressLint("SdCardPath")
    public static String copySoDesDir() {
      try {
        if (TextUtils.isEmpty(COPY_SO_DES_DIR)) {
          if (sApplication == null) {
            WXLogUtils.e("sApplication is null, so copy path will be null");
            return null;
          }
  
          String dirName = "/cache/weex/libs";
          File desDir = null;
          String cachePath = WXEnvironment.getApplication().getApplicationContext().getCacheDir().getPath();
  
          if (TextUtils.isEmpty(cachePath)) {
            desDir = new File(cachePath, dirName);
          } else {
            String pkgName = sApplication.getPackageName();
            String toPath = "/data/data/" + pkgName + dirName;
            desDir = new File(toPath);
          }
  
          if (!desDir.exists()) {
            desDir.mkdirs();
          }
          COPY_SO_DES_DIR = desDir.getAbsolutePath();
        }
      } catch (Throwable e) {
        WXLogUtils.e(WXLogUtils.getStackTrace(e));
      }
      return COPY_SO_DES_DIR;
  
    }
  
    @Deprecated
    /**
     * Use {@link #isHardwareSupport()} if you want to see whether current hardware support Weex.
     */
    public static boolean isSupport() {
      boolean isInitialized = WXSDKEngine.isInitialized();
      if(!isInitialized){
        WXLogUtils.e("WXSDKEngine.isInitialized():" + isInitialized);
      }
      return isHardwareSupport() && isInitialized;
    }
  
    public static boolean isLayoutDirectionRTL() {
      // support RTL
      if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
        return sApplication.getApplicationContext().getResources().getBoolean(R.bool.weex_is_right_to_left);
      }
      return false;
    }
    /**
     * Tell whether Weex can run on current hardware.
     * @return true if weex can run on current hardware, otherwise false.
     * Weex has removed the restrictions on the tablet, please use {@link #isCPUSupport()}
     */
    @Deprecated
    public static boolean isHardwareSupport() {
      if (WXEnvironment.isApkDebugable()) {
        WXLogUtils.d("isTableDevice:" + WXUtils.isTabletDevice());
      }
      return isCPUSupport();
    }
  
    /**
     * Determine whether Weex supports the current CPU architecture
     * @return true when support
     */
    public static boolean isCPUSupport(){
      boolean excludeX86 = "true".equals(getCustomOptions().get(SETTING_EXCLUDE_X86SUPPORT));
      boolean isX86AndExcluded = WXSoInstallMgrSdk.isX86() && excludeX86;
      boolean isCPUSupport = WXSoInstallMgrSdk.isCPUSupport() && !isX86AndExcluded;
      if (WXEnvironment.isApkDebugable()) {
        WXLogUtils.d("WXEnvironment.sSupport:" + isCPUSupport
                + "isX86AndExclueded: "+ isX86AndExcluded);
      }
      return isCPUSupport;
    }
  
    public static boolean isApkDebugable() {
      return isApkDebugable(sApplication);
    }
  
    public static boolean isApkDebugable(Application application) {
      if (application == null) {
        return false;
      }
  
      if (isPerf) {
        return false;
      }
  
      if (sDebugFlagInit){
        return isApkDebug;
      }
      try {
        String debugModeConfig = getCustomOptions().get(WXConfig.debugMode);
        if (TextUtils.isEmpty(debugModeConfig)){
          ApplicationInfo info = application.getApplicationInfo();
          isApkDebug = (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
        }else {
          isApkDebug = Boolean.valueOf(debugModeConfig);
        }
      } catch (Exception e) {
        /**
         * Don't call WXLogUtils.e here,will cause stackoverflow
         */
        e.printStackTrace();
        isApkDebug = false;
      }
      sDebugFlagInit = true;
      return isApkDebug;
    }
  
    public static boolean isPerf() {
      return isPerf;
    }
  
    @SuppressLint("HardwareIds")
    private static String getDevId() {
      String ret = "";
      if(sApplication != null){
        try{
          ret = ((TelephonyManager) sApplication
              .getSystemService(Context.TELEPHONY_SERVICE)).getDeviceId();
        }catch (SecurityException | NullPointerException e){
          WXLogUtils.e(WXLogUtils.getStackTrace(e));
        }
      }
      return ret;
    }
  
    public static Application getApplication() {
      return sApplication;
    }
  
    public void initMetrics() {
      if (sApplication == null) {
        return;
      }
    }
  
    public static String getDiskCacheDir(Context context) {
      if (context == null) {
        return null;
      }
      String cachePath = null;
      try {
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
                || !Environment.isExternalStorageRemovable()) {
          cachePath = context.getExternalCacheDir().getPath();
        } else {
          cachePath = context.getCacheDir().getPath();
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
      return cachePath;
    }
  
    public static String getFilesDir(Context context) {
      if (context == null) {
        return "";
      }
      File filesDir = context.getFilesDir();
      String path = "";
      if (filesDir != null) {
        path = filesDir.getPath();
      } else {
        path = WXEnvironment.getApplication().getApplicationInfo().dataDir;
        path += File.separator;
        path += "files";
      }
  
      return path;
    }
  
    public static String getCrashFilePath(Context context) {
      if (context == null) {
          return "";
      }
  
      File dir = context.getDir("crash", Context.MODE_PRIVATE);
      if (dir == null)
          return "";
  
      String crashDir = dir.getAbsolutePath();
  
      return crashDir;
    }
  
    public static String getGlobalFontFamilyName() {
      return sGlobalFontFamily;
    }
  
    public static void setGlobalFontFamily(String fontFamilyName, Typeface typeface) {
      WXLogUtils.d("GlobalFontFamily", "Set global font family: " + fontFamilyName);
      sGlobalFontFamily = fontFamilyName;
      if (!TextUtils.isEmpty(fontFamilyName)) {
        if (typeface == null) {
          TypefaceUtil.removeFontDO(fontFamilyName);
        } else {
          FontDO nativeFontDO = new FontDO(fontFamilyName, typeface);
          TypefaceUtil.putFontDO(nativeFontDO);
          WXLogUtils.d("TypefaceUtil", "Add new font: " + fontFamilyName);
        }
      }
    }
  
    public static boolean isOpenDebugLog() {
      return openDebugLog;
    }
  
    public static void setOpenDebugLog(boolean openDebugLog) {
      WXEnvironment.openDebugLog = openDebugLog;
    }
  
    public static void  setApkDebugable(boolean debugable){
      isApkDebug  = debugable;
      if(!isApkDebug){
        openDebugLog = false;
      }
    }
  
    public static String getCacheDir() {
      final Application application = getApplication();
      if (application == null || application.getApplicationContext() == null)
        return null;
      return application.getApplicationContext().getCacheDir().getPath();
    }
  
    public static String extractSo() {
      File sourceFile = new File(getApplication().getApplicationContext().getApplicationInfo().sourceDir);
      final String soDesPath = copySoDesDir();
      if (sourceFile.exists() && !TextUtils.isEmpty(soDesPath)) {
        try {
          WXFileUtils.extractSo(sourceFile.getAbsolutePath(), soDesPath);
        } catch (IOException e) {
          WXLogUtils.e("extractSo error " + e.getMessage());
  //        e.printStackTrace();
          return null;
        }
        return soDesPath;
      }
      return null;
    }
  
    private static String findIcuPath() {
      File file = new File("/proc/self/maps");
      BufferedReader reader = null;
      try {
        reader = new BufferedReader(new FileReader(file));
        String tempString = null;
        while ((tempString = reader.readLine()) != null) {
          if (tempString.contains("icudt")) {
  
            int i = tempString.indexOf('/');
  
            String substring = tempString.substring(i);
            return substring.trim();
          }
        }
        reader.close();
      } catch (IOException e) {
        e.printStackTrace();
      } finally {
        if (reader != null) {
          try {
            reader.close();
          } catch (IOException e1) {
          }
        }
      }
  
      return null;
    }
  
    public static int memfd_create(String name, int size) {
      if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
        return 0;
      }
  
      int fd = 0;
      try {
        Method memfd_create = Os.class.getMethod("memfd_create", String.class, int.class);
        Object fd_object = memfd_create.invoke(null, name, 0);
        if (fd_object instanceof FileDescriptor) {
          Field descriptor = FileDescriptor.class.getDeclaredField("descriptor");
          descriptor.setAccessible(true);
          Object fd_int_object = descriptor.get(fd_object);
          fd = Integer.parseInt(String.valueOf(fd_int_object));
          descriptor.setAccessible(false);
          Os.ftruncate((FileDescriptor) fd_object, size);
        }
      } catch (Throwable e) {
        // do Nothing
      }
      return fd;
    }
  
    public static String findSoPath(String libName) {
      String soPath = ((PathClassLoader) (WXEnvironment.class.getClassLoader())).findLibrary(libName);
      if (!TextUtils.isEmpty(soPath)) {
        File soFile = new File(soPath);
        if (soFile.exists()) {
          WXLogUtils.e(libName + "'s Path is" + soPath);
          return soFile.getAbsolutePath();
        } else {
          WXLogUtils.e(libName + "'s Path is " + soPath + " but file does not exist");
        }
      }
  
      String realName = "lib" + libName + ".so";
      String cacheDir = getCacheDir();
      if (TextUtils.isEmpty(cacheDir)) {
        WXLogUtils.e("cache dir is null");
        return "";
      }
  
  
      if (cacheDir.indexOf("/cache") > 0) {
        soPath = new File(cacheDir.replace("/cache", "/lib"), realName).getAbsolutePath();
      }
  
  
      final File soFile = new File(soPath);
      if (soFile.exists()) {
        WXLogUtils.e(libName + "use lib so");
        return soPath;
      } else {
        //unzip from apk file
        final String extractSoPath = extractSo();
        if (!TextUtils.isEmpty(extractSoPath)) {
          return new File(extractSoPath, realName).getAbsolutePath();
        }
      }
      return soPath;
    }
  
    public static String getLibJScRealPath() {
      if(TextUtils.isEmpty(CORE_JSC_SO_PATH)) {
        CORE_JSC_SO_PATH = findSoPath(CORE_JSC_SO_NAME);
        WXLogUtils.e("findLibJscRealPath " + CORE_JSC_SO_PATH);
      }
      return CORE_JSC_SO_PATH;
    }
  
    public static String getLibJssRealPath() {
      if(TextUtils.isEmpty(CORE_JSS_SO_PATH)) {
        CORE_JSS_SO_PATH = findSoPath(CORE_JSS_SO_NAME);
        WXLogUtils.d("test-> findLibJssRealPath " + CORE_JSS_SO_PATH);
      }
  
      return CORE_JSS_SO_PATH;
    }
  
    public static String getLibJssIcuPath() {
      if(TextUtils.isEmpty(CORE_JSS_ICU_PATH)){
        CORE_JSS_ICU_PATH = findIcuPath();
      }
  
      return CORE_JSS_ICU_PATH;
    }
  
    public static String getLibLdPath() {
      if (TextUtils.isEmpty(LIB_LD_PATH)) {
        ClassLoader classLoader = WXEnvironment.class.getClassLoader();
        try {
          Method getLdLibraryPath = classLoader.getClass().getMethod("getLdLibraryPath", new Class[0]);
          LIB_LD_PATH = (String) getLdLibraryPath.invoke(classLoader, new Object[0]);
        } catch (IllegalAccessException e) {
          e.printStackTrace();
        } catch (InvocationTargetException e) {
          e.printStackTrace();
        } catch (NoSuchMethodException e) {
          e.printStackTrace();
        }
      }
  
      if(TextUtils.isEmpty(LIB_LD_PATH)) {
        try {
          String property = System.getProperty("java.library.path");
          String libJScRealPath = getLibJScRealPath();
          if(!TextUtils.isEmpty(libJScRealPath)) {
            LIB_LD_PATH = new File(libJScRealPath).getParent() + ":" + property;
          }
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
  
      WXLogUtils.d("getLibLdPath is " + LIB_LD_PATH);
      return LIB_LD_PATH;
    }
  
    public static class WXDefaultSettings {
      private String configName = "weex_default_settings";
      private SharedPreferences sharedPreferences = null;
      public WXDefaultSettings(Application application) {
        if(application != null) {
          sharedPreferences = application.getSharedPreferences(configName, MODE_PRIVATE);
        }
      }
  
      public synchronized String getValue(String key, String defaultValue) {
        if(sharedPreferences == null || TextUtils.isEmpty(key)) {
          WXLogUtils.i("get default settings " + key + " return default value :" + defaultValue);
          return defaultValue;
        }
  
        String result = sharedPreferences.getString(key, defaultValue);
        WXLogUtils.i("get default settings " + key + " : " + result);
        return result;
      }
  
      public synchronized void saveValue(String key, String value) {
        if (sharedPreferences == null
                || TextUtils.isEmpty(key)
                || TextUtils.isEmpty(value)) {
          return;
        }
        WXLogUtils.i("save default settings " + key + ":" + value);
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.putString(key, value);
        editor.apply();
      }
    }
  }
  