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.utils;
  
  import android.annotation.SuppressLint;
  import android.content.Context;
  import android.content.res.Resources;
  import android.graphics.Canvas;
  import android.graphics.Color;
  import android.graphics.Paint;
  import android.graphics.Path;
  import android.graphics.PixelFormat;
  import android.graphics.Point;
  import android.graphics.RectF;
  import android.graphics.drawable.BitmapDrawable;
  import android.graphics.drawable.ColorDrawable;
  import android.graphics.drawable.Drawable;
  import android.graphics.drawable.LayerDrawable;
  import android.os.Build;
  import android.os.Build.VERSION_CODES;
  import android.support.annotation.IntDef;
  import android.support.annotation.NonNull;
  import android.support.annotation.Nullable;
  import android.util.DisplayMetrics;
  import android.view.View;
  import android.view.ViewGroup;
  import android.view.ViewGroup.LayoutParams;
  import android.view.WindowManager;
  
  import org.apache.weex.WXEnvironment;
  import org.apache.weex.WXSDKInstance;
  import org.apache.weex.WXSDKManager;
  import org.apache.weex.common.Constants;
  import org.apache.weex.common.WXErrorCode;
  import org.apache.weex.common.WXRuntimeException;
  import org.apache.weex.ui.component.WXComponent;
  import org.apache.weex.ui.flat.widget.Widget;
  import org.apache.weex.ui.flat.widget.WidgetGroup;
  import org.apache.weex.ui.view.border.BorderDrawable;
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.util.concurrent.atomic.AtomicInteger;
  
  /**
   * Utility class for views
   */
  public class WXViewUtils {
  
    @IntDef({PixelFormat.TRANSLUCENT, PixelFormat.TRANSPARENT, PixelFormat.OPAQUE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Opacity {}
  
    /**
     * Use {@link PixelFormat#TRANSLUCENT} instead
     */
    @Deprecated
    public static final int TRANSLUCENT = PixelFormat.TRANSLUCENT;
  
    /**
     * Use {@link PixelFormat#TRANSPARENT} instead.
     */
    @Deprecated
    public static final int TRANSPARENT = PixelFormat.TRANSPARENT;
  
    /**
     * Use {@link PixelFormat#OPAQUE} instead
     */
    @Deprecated
    public static final int OPAQUE = PixelFormat.OPAQUE;
  
    public static final int DIMENSION_UNSET = -1;
    private static final boolean mUseWebPx = false;
  
    private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
  
    @SuppressLint("NewApi")
    public static int generateViewId() {
  
      if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
        for (;;) {
          final int result = sNextGeneratedId.get();
          // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
          int newValue = result + 1;
          if (newValue > 0x00FFFFFF)
            newValue = 1; // Roll over to 1, not 0.
          if (sNextGeneratedId.compareAndSet(result, newValue)) {
            return result;
          }
        }
      } else {
        return View.generateViewId();
      }
    }
  
    private static int mScreenWidth;
    private static int mScreenHeight;
  
  
    public static int getWeexHeight(String instanceId){
      WXSDKInstance instance = WXSDKManager.getInstance().getSDKInstance(instanceId);
      if (instance != null) {
        int weexHeight = instance.getWeexHeight();
        if (weexHeight >= 0 || weexHeight == -2) {
          return weexHeight;
        }
        else {
          return getScreenHeight(WXEnvironment.sApplication);
        }
      }
      return -3;
    }
  
  
  
    public static int getWeexWidth(String instanceId){
      WXSDKInstance instance = WXSDKManager.getInstance().getSDKInstance(instanceId);
      if (instance != null) {
        int weexWidth = instance.getWeexWidth();
        if (weexWidth >= 0 || weexWidth == -2) {
          return weexWidth;
        }
        else {
          return getScreenWidth(WXEnvironment.sApplication);
        }
      }
      return -3;
    }
  
    @Deprecated
    public static int getScreenWidth( ) {
      return getScreenWidth(WXEnvironment.sApplication);
    }
  
    @Deprecated
    public static int setScreenWidth(int screenWidth) {
      return mScreenWidth = screenWidth;
    }
  
    public static float getScreenDensity(Context ctx){
      if(ctx != null){
        try{
          Resources res = ctx.getResources();
          return res.getDisplayMetrics().density;
        }catch (Exception e){
          WXLogUtils.e("getScreenDensityDpi exception:"+e.getMessage());
        }
      }
      return Constants.Value.DENSITY;
    }
  
    public static void updateApplicationScreen(Context context){
      if(context == null || WXEnvironment.sApplication == null){
          return;
      }
      DisplayMetrics metrics = context.getResources().getDisplayMetrics();
      DisplayMetrics displayMetrics = WXEnvironment.sApplication.getResources().getDisplayMetrics();
      displayMetrics.heightPixels = metrics.heightPixels;
      displayMetrics.widthPixels = metrics.widthPixels;
      displayMetrics.density = metrics.density;
      displayMetrics.densityDpi = metrics.densityDpi;
      displayMetrics.scaledDensity = metrics.scaledDensity;
      displayMetrics.xdpi = metrics.xdpi;
    }
  
    public static int getScreenWidth(Context ctx) {
      if(ctx!=null){
        Resources res = ctx.getResources();
        mScreenWidth = res.getDisplayMetrics().widthPixels;
        if(WXEnvironment.SETTING_FORCE_VERTICAL_SCREEN){
          mScreenHeight = res
                  .getDisplayMetrics()
                  .heightPixels;
          mScreenWidth = mScreenHeight > mScreenWidth ? mScreenWidth : mScreenHeight;
        }
      } else if(WXEnvironment.isApkDebugable()){
        throw new WXRuntimeException("Error Context is null When getScreenHeight");
      }
      return mScreenWidth;
    }
  
  
    public static int getStatusBarHeight(Context context){
        Resources resources = context.getResources();
        int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
          int statusBarHeight = resources.getDimensionPixelSize(resourceId);
          return statusBarHeight;
        }
        return -1;
    }
  
  
    @Deprecated
    public static int getScreenHeight() {
      return getScreenHeight(WXEnvironment.sApplication);
    }
  
  
    public static int getScreenHeight(String instanceId){
      WXSDKInstance instance = WXSDKManager.getInstance().getSDKInstance(instanceId);
      return instance.isFullScreenHeightEnabled()?getFullScreenHeight(WXEnvironment.sApplication):getScreenHeight(WXEnvironment.sApplication);
    }
  
  //get screen height with status bar on full screen
    public static int getFullScreenHeight(Context cxt) {
      if(cxt!=null){
        WindowManager wm;
        Resources res = cxt.getResources();
        if(Build.VERSION.SDK_INT >= 17 && (wm = (WindowManager)cxt.getSystemService(Context.WINDOW_SERVICE)) != null
                && wm.getDefaultDisplay() != null){
          Point size = new Point();
          wm.getDefaultDisplay().getRealSize(size);
          mScreenHeight = size.y;
        }
        else {
          mScreenHeight = cxt.getResources().getDisplayMetrics().heightPixels;
        }
        if(WXEnvironment.SETTING_FORCE_VERTICAL_SCREEN){
          mScreenWidth = res
                  .getDisplayMetrics()
                  .widthPixels;
          mScreenHeight = mScreenHeight > mScreenWidth ? mScreenHeight : mScreenWidth;
        }
      } else if (WXEnvironment.isApkDebugable()){
        throw new WXRuntimeException("Error Context is null When getScreenHeight");
      }
      return mScreenHeight;
    }
  //  get screen height without status bar
    public static int getScreenHeight(Context cxt) {
      if(cxt!=null){
        Resources res = cxt.getResources();
        mScreenHeight = res.getDisplayMetrics().heightPixels;
        if(WXEnvironment.SETTING_FORCE_VERTICAL_SCREEN){
          mScreenWidth = res
                  .getDisplayMetrics()
                  .widthPixels;
          mScreenHeight = mScreenHeight > mScreenWidth ? mScreenHeight : mScreenWidth;
        }
      } else if (WXEnvironment.isApkDebugable()){
        throw new WXRuntimeException("Error Context is null When getScreenHeight");
      }
      return mScreenHeight;
    }
  
  
    /**
     * Convert distance from JS,CSS to native. As the JS considers the width of the screen is 750px.
     * There must be a transform when accessing distance from JS,CSS and use it.
     * Basically, this method calculates a scale factor(ScreenWidth/750) and use apply this scale
     * factor to JS,CSS distance.
     * @param pxValue the raw distance from JS or CSS. The result will be rounded to a closet int.
     * @return the actual distance in the screen.
     */
  
    @Deprecated
    public static float getRealPxByWidth(float pxValue) {
      return getRealPxByWidth(pxValue,750);
    }
    public static float getRealPxByWidth(float pxValue,int customViewport) {
      if (Float.isNaN(pxValue)) {
        return pxValue;
      }
      if (mUseWebPx) {
        return (float) Math.rint(pxValue);
      } else {
        float realPx = (pxValue * getScreenWidth() / customViewport);
        return realPx > 0.005 && realPx < 1 ? 1 : (float) Math.rint(realPx);
      }
    }
  
    @Deprecated
    public static float getRealSubPxByWidth(float pxValue) {
      return getRealSubPxByWidth(pxValue,750);
    }
  
    public static float getRealSubPxByWidth(float pxValue,int customViewport) {
      if (Float.isNaN(pxValue)) {
        return pxValue;
      }
      if (mUseWebPx) {
        return (float) Math.rint(pxValue);
      } else {
        float realPx = (pxValue * getScreenWidth() / customViewport);
        return realPx > 0.005 && realPx < 1 ? 1 : realPx;
      }
    }
  
    /**
     *  Internal interface that just for debug, you should never call this method because of accuracy loss obviously
     */
    @Deprecated
    public static float getWeexPxByReal(float pxValue) {
      return getWeexPxByReal(pxValue,750);
    }
  
    public static float getWeexPxByReal(float pxValue,int customViewport) {
      if (Float.isNaN(pxValue)) {
        return pxValue;
      }
      if (mUseWebPx) {
        return (float) Math.rint(pxValue);
      } else {
        return pxValue * customViewport / getScreenWidth();
      }
    }
  
    @Deprecated
    public static float getRealPxByWidth2(float pxValue) {
      return getRealPxByWidth2(pxValue,750);
    }
    public static int getRealPxByWidth2(float pxValue,int customViewport) {
      if (mUseWebPx) {
        return (int) pxValue;
      } else {
        float realPx = (pxValue * getScreenWidth() / customViewport);
        return realPx > 0.005 && realPx < 1 ? 1 : (int) realPx - 1;
      }
    }
  
    /**
     * Convert distance from native to JS,CSS. As the JS considers the width of the screen is 750px.
     * There must be a transform when return distance to JS,CSS.
     * Basically, this method calculates a scale factor(ScreenWidth/750) and use apply this scale
     * factor to native distance.
     * @param pxValue the raw distance of native. The result will be rounded to a closet int.
     * @return the distance in JS,CSS where the screenWidth is 750 px.
     */
    @Deprecated
    public static float getWebPxByWidth(float pxValue) {
      return getWebPxByWidth(pxValue,750);
    }
  
    public static float getWebPxByWidth(float pxValue,int customViewport) {
      if (pxValue < -1.9999 && pxValue > -2.005) {
        return Float.NaN;
      }
      if (mUseWebPx) {
        return pxValue;
      } else {
        float realPx = (pxValue * customViewport / getScreenWidth());
        return realPx > 0.005 && realPx < 1 ? 1 : realPx;
      }
    }
  
  
    /**
     * Convert dp to px
     * @param dpValue the dp value to be converted
     * @return the px value
     */
    public static int dip2px(float dpValue) {
      float scale = 2;
      try {
        scale = WXEnvironment.getApplication().getResources()
                .getDisplayMetrics().density;
      } catch (Exception e) {
        WXLogUtils.e("[WXViewUtils] dip2px:", e);
      }
      float finalPx = (dpValue * scale + 0.5f);
      return finalPx > 0 && finalPx < 1 ? 1 : (int) finalPx;
    }
  
    public static boolean onScreenArea(View view) {
      if (view == null || view.getVisibility() != View.VISIBLE) {
        return false;
      }
  
      int[] p = new int[2];
      view.getLocationOnScreen(p);
      LayoutParams lp = view.getLayoutParams();
      int viewH = 0;
      if (lp != null) {
        viewH = lp.height;
      } else {
        viewH = view.getHeight();
      }
  
      return (p[1] > 0 && (p[1] - WXViewUtils.getScreenHeight(WXEnvironment.sApplication) < 0))
              || (viewH + p[1] > 0 && p[1] <= 0);
    }
  
    /**
     * Multiplies the color with the given alpha.
     *
     * @param color color to be multiplied
     * @param alpha value between 0 and 255
     * @return multiplied color
     */
    public static int multiplyColorAlpha(int color, int alpha) {
      if (alpha == 255) {
        return color;
      }
      if (alpha == 0) {
        return color & 0x00FFFFFF;
      }
      alpha = alpha + (alpha >> 7); // make it 0..256
      int colorAlpha = color >>> 24;
      int multipliedAlpha = colorAlpha * alpha >> 8;
      return (multipliedAlpha << 24) | (color & 0x00FFFFFF);
    }
  
    @Opacity
    public static int getOpacityFromColor(int color) {
      int colorAlpha = color >>> 24;
      if (colorAlpha == 255) {
        return PixelFormat.OPAQUE;
      } else if (colorAlpha == 0) {
        return PixelFormat.TRANSPARENT;
      } else {
        return PixelFormat.TRANSLUCENT;
      }
    }
  
    @SuppressWarnings("deprecation")
    public static void setBackGround(View view, Drawable drawable, WXComponent component) {
      if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN){
        view.setBackgroundDrawable(drawable);
      }
      else{
        try {
          view.setBackground(drawable);
        } catch (Exception e) {
          if (component == null)
            return;
          WXExceptionUtils.commitCriticalExceptionRT(component.getInstanceId(),
                  WXErrorCode.WX_RENDER_ERR_TEXTURE_SETBACKGROUND,
                  component.getComponentType() + " setBackGround for android view",
                  WXErrorCode.WX_RENDER_ERR_TEXTURE_SETBACKGROUND.getErrorMsg() + ": TextureView doesn't support displaying a background drawable!",
                  null);
        }
      }
    }
  
    public static @Nullable
    BorderDrawable getBorderDrawable(@NonNull View view){
      Drawable drawable=view.getBackground();
      if(drawable instanceof BorderDrawable){
        return (BorderDrawable) drawable;
      }
      else if(drawable instanceof LayerDrawable){
        if(((LayerDrawable) drawable).getNumberOfLayers()>1) {
          Drawable innerDrawable=((LayerDrawable) drawable).getDrawable(0);
          if(innerDrawable instanceof BorderDrawable){
            return (BorderDrawable) innerDrawable;
          }
        }
      }
      return null;
    }
  
    public static void clipCanvasWithinBorderBox(View targetView, Canvas canvas) {
      Drawable drawable;
      if (clipCanvasDueToAndroidVersion(canvas) &&
              clipCanvasIfAnimationExist(targetView) &&
              ((drawable = targetView.getBackground()) instanceof BorderDrawable)) {
        BorderDrawable borderDrawable = (BorderDrawable) drawable;
        if (borderDrawable.isRounded()) {
          if (clipCanvasIfBackgroundImageExist(targetView, borderDrawable)) {
            Path path = borderDrawable.getContentPath(
                    new RectF(0, 0, targetView.getWidth(), targetView.getHeight()));
            canvas.clipPath(path);
          }
        }
      }
    }
  
    public static void clipCanvasWithinBorderBox(Widget widget, Canvas canvas) {
      BorderDrawable borderDrawable;
      if (clipCanvasDueToAndroidVersion(canvas) &&
              clipCanvasIfAnimationExist(null) &&
              (borderDrawable=widget.getBackgroundAndBorder())!=null ) {
        if (borderDrawable.isRounded() && clipCanvasIfBackgroundImageExist(widget, borderDrawable)) {
          Path path = borderDrawable.getContentPath(
                  new RectF(0, 0, widget.getBorderBox().width(), widget.getBorderBox().height()));
          canvas.clipPath(path);
        }
        else {
          canvas.clipRect(widget.getBorderBox());
        }
      }
    }
  
    /**
     * According to https://developer.android.com/guide/topics/graphics/hardware-accel.html#unsupported
     API 18 or higher supports clipPath to canvas based on hardware acceleration.
     * @param canvas
     * @return
     */
    private static boolean clipCanvasDueToAndroidVersion(Canvas canvas) {
      return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 ||
              !canvas.isHardwareAccelerated();
    }
  
    /**
     * According to https://code.google.com/p/android/issues/detail?id=225556&sort=-id&colspec=ID
     * clipPath doesn't work with rotation nor scale when API level is 24.
     * As animation will not cause redraw if hardware-acceleration enabled, clipCanvas feature has
     * to be disabled when API level is 24 without considering the animation property.
     */
    private static boolean clipCanvasIfAnimationExist(View targetView) {
      if (Build.VERSION.SDK_INT != VERSION_CODES.N) {
        return true;
      } else {
        return false;
      }
    }
  
    /**
     * Due limitation in Android platform, the linear gradient in the following page will not be
     * rounded if {@link Canvas#clipPath(Path)} of the parent view invoked when API level is lower
     * than 21.
     * http://dotwe.org/weex/963c9ade129f86757cecdd85651cd30e
     * @param targetView
     * @param borderDrawable
     * @return
     */
    private static boolean clipCanvasIfBackgroundImageExist(@NonNull View targetView,
                                                            @NonNull BorderDrawable borderDrawable) {
      if (targetView instanceof ViewGroup) {
        View child;
        ViewGroup parent = ((ViewGroup) targetView);
        int count = parent.getChildCount();
        for (int i = 0; i < count; i++) {
          child = parent.getChildAt(i);
          if (child.getBackground() instanceof BorderDrawable &&
                  ((BorderDrawable) child.getBackground()).hasImage() &&
                  Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            return false;
          }
        }
      }
      return true;
    }
  
    private static boolean clipCanvasIfBackgroundImageExist(@NonNull Widget widget,
                                                            @NonNull BorderDrawable borderDrawable) {
      if (widget instanceof WidgetGroup) {
        for (Widget child : ((WidgetGroup) widget).getChildren()) {
          if (child.getBackgroundAndBorder().hasImage() &&
                  Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            return false;
          }
        }
      }
      return true;
    }
  
    public static boolean isViewVisible(View v) {
      if (null == v){
        return false;
      }
  
      boolean isAttachToWindow = Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT
          ?v.isAttachedToWindow()
          :v.getWindowToken() != null;
  
      if (!isAttachToWindow){
        return false;
      }
      if (v.getVisibility() != View.VISIBLE || v.getAlpha()<=0){
        return false;
      }
  
      Drawable bacDrawable = v.getBackground();
      if (null == bacDrawable){
        return true;
      }
      if (Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT){
        return bacDrawable.getAlpha()>0;
      }
      //< 4.4
      if (bacDrawable instanceof ColorDrawable){
        int alpha = Color.alpha(((ColorDrawable) bacDrawable).getColor());
        return alpha >0;
      }else if (bacDrawable instanceof BitmapDrawable){
        Paint paint = ((BitmapDrawable) bacDrawable).getPaint();
        return paint.getAlpha() > 0;
      }
      return true;
    }
  }
  