날짜별 글 목록: 2019년 1월 31일

안드로이드 앱이 동작하는 폼 팩터(Form Factor) 확인 방법

폼 팩터(Form Factor)의 정의를 위키피디아에서 살펴보면 아래와 같다.

컴퓨터 시스템의 각 부품의 물리적 치수의 형태를 의미한다.

즉, 소프트웨어가 동작하는 기기의 형태를 말하는 것이다. 안드로이드 적용범위가 확대되면서, 안드로이드 앱이 실행할 수 있는 하드웨어 기기가 많아졌다. 예로, 크롬북이나 TV등을 예로 들 수 있다.

최근에 안드로이드 앱을 크롬북과 삼성 덱스(DEX)의 UI/UX에 어울리도록 작업을 하면서 특정 기능(키보드 등)은 모바일/태블릿에서만 필요하다. 그래서 앱이 동작하는 기기의 정보를 확인해야 했고, 아래의 코드로 해결을 했다.

아래 코드는 구글 TalkBack 깃헙 사이트의 https://github.com/google/talkback/blob/master/utils/src/main/java/FormFactorUtils.java 코드에 삼성 덱스(DEX)를 확인하는 코드를 추가했다.

아래의 ARC는 App Runtime for Chrome의 줄임말으로 폼 팩터가 크롬북이라는 것을 알 수 있다.

import android.app.UiModeManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.os.Build;

public class FormFactorUtils {
	private static final int FORM_FACTOR_PHONE_OR_TABLET = 0;
	private static final int FORM_FACTOR_WATCH = 1;
	private static final int FORM_FACTOR_TV = 2;
	private static final int FORM_FACTOR_ARC = 3;

	private static final int FORM_FACTOR_SAMSUNG_DEX = 100;

	private static final String ARC_DEVICE_PATTERN = ".+_cheets|cheets_.+";

	private static FormFactorUtils sInstance;

	private final int mFormFactor;
	private final boolean mHasAccessibilityShortcut;

	private FormFactorUtils(final Context context) {
		// Find device type.
		if (context.getApplicationContext()
				.getPackageManager()
				.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
			mFormFactor = FORM_FACTOR_WATCH;
		} else if (isContextTelevision(context)) {
			mFormFactor = FORM_FACTOR_TV;
		} else if (Build.DEVICE != null && Build.DEVICE.matches(ARC_DEVICE_PATTERN)) {
			mFormFactor = FORM_FACTOR_ARC;
		} else if(isContextDex(context)) {
			mFormFactor = FORM_FACTOR_SAMSUNG_DEX;
		} else {
			mFormFactor = FORM_FACTOR_PHONE_OR_TABLET;
		}

		// Find whether device supports accessibility shortcut.
		mHasAccessibilityShortcut = (BuildVersionUtils.isAtLeastO() && mFormFactor == FORM_FACTOR_PHONE_OR_TABLET);
	}

	/** @return an instance of this Singleton. */
	public static synchronized FormFactorUtils getInstance(final Context context) {
		if (sInstance == null) {
			sInstance = new FormFactorUtils(context);
		}
		return sInstance;
	}

	/** Return the cached version of the isWatch. */
	public boolean isWatch() {
		return mFormFactor == FORM_FACTOR_WATCH;
	}

	public boolean isArc() {
		return mFormFactor == FORM_FACTOR_ARC;
	}

	public boolean isTv() {
		return mFormFactor == FORM_FACTOR_TV;
	}

	public boolean isPhoneOrTablet() {
		return mFormFactor == FORM_FACTOR_PHONE_OR_TABLET;
	}

	public boolean isDex() {
		return mFormFactor == FORM_FACTOR_SAMSUNG_DEX;
	}

	public boolean hasAccessibilityShortcut() {
		return mHasAccessibilityShortcut;
	}

	public static boolean useSpeakPasswordsServicePref() {
		return BuildVersionUtils.isAtLeastO();
	}

	public static boolean isContextTelevision(Context context) {
		if (context == null) {
			return false;
		}

		UiModeManager modeManager = (UiModeManager) context.getSystemService(Context.UI_MODE_SERVICE);
		return modeManager != null
				&& modeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION;
	}

	private static boolean isContextDex(Context context) {
		if (context == null) {
			return false;
		}

		try {
			Configuration config = context.getResources().getConfiguration();
			Class configClass = config.getClass();
			if (configClass.getField("SEM_DESKTOP_MODE_ENABLED").getInt(configClass)
					== configClass.getField("semDesktopModeEnabled").getInt(config)) {
				return true;
			}
		} catch (Exception e) {
			// ignore
		}

		return false;
	}
}

웹뷰(WebView)에 OnClick 이벤트 추가하기

웹뷰(WebView)는 View를 상속받아서, 뷰에서 클릭 이벤트를 처리할 수 있도록 OnClickListener를 등록할 수 있다. 하지만, 웹뷰는 OnClickListener로 이벤트를 알려 주지 않는다. 그래서, OnClick 이벤트를 처리하기 위해서는 이 이벤트에 대한 처리를 직접해야 한다.

웹뷰의 OnClick 이벤트를 캐치하기 위해서는 OnTouch 이벤트에서 조건에 맞는 경우에 OnClick으로 간주하면 된다.

조건은 아래의 코드에서 볼 수 있듯이 다음과 같다.
– 클릭시 1초이내(1초를 넘어가면 롱클릭)에 손가락이 떨어져야 한다.
– 그리고 손가락의 이동이 15dp이내이어야 한다. 그 이상 멀어진 경우 드레그를 했다고 간주한다.

   
import android.content.Context;
import android.view.MotionEvent;
import android.view.View;

public class OnClickWithOnTouchListener implements View.OnTouchListener {
	public interface OnClickListener {
		void onClick();
	}

	/**
	 * Max allowed duration for a "click", in milliseconds.
	 */
	static final int MAX_CLICK_DURATION = 1000;
	/**
	 * Max allowed distance to move during a "click", in DP.
	 */
	static final int MAX_CLICK_DISTANCE = 15;

	private Context mContext;
	private OnClickListener mClickListener;

	private long pressStartTime;
	private float pressedX;
	private float pressedY;
	private boolean stayedWithinClickDistance;

	public OnClickWithOnTouchListener(Context context, OnClickListener clickListener) {
		this.mContext = context;
		this.mClickListener = clickListener;
	}

	@Override
	public boolean onTouch(View v, MotionEvent event) {
		if(mClickListener == null) {
			return false;
		}

		int eventAction = event.getAction();
		if(eventAction == MotionEvent.ACTION_DOWN) {
			pressStartTime = System.currentTimeMillis();
			pressedX = event.getX();
			pressedY = event.getY();
			stayedWithinClickDistance = true;
		} else if(eventAction == MotionEvent.ACTION_MOVE) {
			float distance = distance(pressedX, pressedY, event.getX(), event.getY());
			if (stayedWithinClickDistance && distance > MAX_CLICK_DISTANCE) {
				stayedWithinClickDistance = false;
			}
		} else if(eventAction == MotionEvent.ACTION_UP) {
			long pressDuration = System.currentTimeMillis() - pressStartTime;
			if (pressDuration < MAX_CLICK_DURATION && stayedWithinClickDistance) {
				mClickListener.onClick();
			}
		}

		return false;
	}

	private float distance(float x1, float y1, float x2, float y2) {
		float dx = x1 - x2;
		float dy = y1 - y2;
		float distanceInPx = (float) Math.sqrt(dx * dx + dy * dy);
		return pxToDp(mContext, distanceInPx);
	}

	public static float pxToDp(Context context, float px) {
		if(context == null) {
			return 0f;
		}

		return px / context.getResources().getDisplayMetrics().density;
	}
}

이제 위 코드를 사용해서 웹뷰의 OnClick 이벤트를 처리해 보자.

  
	mWebview.setOnTouchListener(new OnClickWithOnTouchListener(this, new OnClickWithOnTouchListener.OnClickListener() {
		@Override
		public void onClick() {
			// onclick 이벤트 처리
		}
	}));

* 레퍼런스
- https://stackoverflow.com/questions/9965695/how-to-distinguish-between-move-and-click-in-ontouchevent/17911861#17911861