어느날 받은 메일함이 기억나서 오늘도 나는 admob center 을 열어 보았다. ㅠㅠ;; 아니나 다를까 또 다른앱의 정책 위반 이야기가 나와 있다.
admob 정책 위반
이번엔 무엇 때문에 이런일이 생길것인가 ??? 내용은 뭐 말 그대로
1. 의도하지 않은 광고 유도 2. 광고프레임 크기 변형
1번의 경우는 어떤때 발생하는 가 ? 아마도 변형된 Toast 때문에 발생한다고 보인다. 앱을 종료할 떄 알림을 주기 위해서 Toast 을 변형하고 그 안에 광고를 넣었다. 그랬더니 앱이 종료를 시도 할 떄 Toast 가 노출 되지만, 실제 앱이 종료된 이후에도 잔상(?)이 남아 있게 되면, 그것을 의도하지 않은 광고 유도 라고 판단하는 것 같다.
2번의 경우는 광고 프레임 변형 이라고 하는데, 그건 아마도 layout 배치를 하는 과정에서 banner 광고를 사용하고 있는데, banner 광고의 전체가 노출 되지 못하도록 layout 을 구성해서 그런것으로 이해가 된다.
OAuth 동의 화면 페이지에서 모든 정보가 완전하고 정확한지 확인하십시오. 특히 앱의 개인정보 보호정책 및 서비스 약관의 URL을 지정했는지 확인하세요.
자격 증명 페이지에서 앱에 대한 Android 클라이언트 ID가 아직 없는 경우 생성합니다. 앱의 패키지 이름과 SHA-1 서명을 지정해야 합니다.
자격 증명 페이지에서 웹 애플리케이션 클라이언트 ID가 아직 없는 경우 생성합니다.승인된 JavaScript 출처 및 승인된 리디렉션 URI 필드를 비워 둘 수 있습니다. 이 클라이언트 ID는 인증 백엔드 서버를 나타냅니다. (서버에서 Google API를 호출할 때 이 클라이언트 ID를 사용하지만 사용하지 않더라도 필요합니다.)
내가 안드로이드앱을 개발 한다고 해서 안드로이드 앱용 client id 을 만들어야 하는 게 아니였다. 그래서 수정본에서는 다음과 같이 구성을 만들었다.
클라이언트 ID을 위해서 등록한 샘플
위 가이드의 설명처럼 자바스크립트 원본 URI나 승인된 리디렉션 URI 등은 입력하지 않아도 된다. 잘 보고 따라 하시길...
import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import android.content.Intent; import android.content.IntentSender; import android.os.Bundle; import android.util.Log; import com.google.android.gms.auth.api.identity.BeginSignInRequest; import com.google.android.gms.auth.api.identity.BeginSignInResult; import com.google.android.gms.auth.api.identity.Identity; import com.google.android.gms.auth.api.identity.SignInClient; import com.google.android.gms.auth.api.identity.SignInCredential; import com.google.android.gms.common.api.ApiException; import com.google.android.gms.common.api.CommonStatusCodes; import com.google.android.gms.tasks.OnFailureListener; import com.google.android.gms.tasks.OnSuccessListener; public class MainActivity2 extends AppCompatActivity { private static final int REQ_ONE_TAP = 100; private static final String TAG = "MainActivity"; boolean showOneTapUI = false ; private SignInClient oneTapClient; private BeginSignInRequest signInRequest; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main2); oneTapClient = Identity.getSignInClient(this); signInRequest = BeginSignInRequest.builder() .setPasswordRequestOptions(BeginSignInRequest.PasswordRequestOptions.builder() .setSupported(true) .build()) .setGoogleIdTokenRequestOptions(BeginSignInRequest.GoogleIdTokenRequestOptions.builder() .setSupported(true) // Your server's client ID, not your Android client ID. .setServerClientId(getString(R.string.default_web_client_id)) // Only show accounts previously used to sign in. .setFilterByAuthorizedAccounts(true) .build()) // Automatically sign in when exactly one credential is retrieved. .setAutoSelectEnabled(true) .build(); oneTapClient.beginSignIn(signInRequest) .addOnSuccessListener(this, new OnSuccessListener<BeginSignInResult>() { @Override public void onSuccess(BeginSignInResult result) { try { Log.e(TAG, "onSuccess..................."); startIntentSenderForResult( result.getPendingIntent().getIntentSender(), REQ_ONE_TAP, null, 0, 0, 0); } catch (IntentSender.SendIntentException e) { Log.e(TAG, "Couldn't start One Tap UI: " + e.getLocalizedMessage()); } } }) .addOnFailureListener(this, new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { // No saved credentials found. Launch the One Tap sign-up flow, or // do nothing and continue presenting the signed-out UI. Log.e(TAG, e.getLocalizedMessage()); } }); } @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case REQ_ONE_TAP: try { SignInCredential credential = oneTapClient.getSignInCredentialFromIntent(data); String idToken = credential.getGoogleIdToken(); String username = credential.getId(); String password = credential.getPassword(); if (idToken != null) { // Got an ID token from Google. Use it to authenticate // with your backend. Log.e(TAG, "Got ID token."); } else if (password != null) { // Got a saved username and password. Use them to authenticate // with your backend. Log.e(TAG, "Got password."); } } catch (ApiException e) { switch (e.getStatusCode()) { case CommonStatusCodes.CANCELED: Log.e(TAG, "One-tap dialog was closed."); // Don't re-prompt the user. showOneTapUI = false; break; case CommonStatusCodes.NETWORK_ERROR: Log.e(TAG, "One-tap encountered a network error."); // Try again or just ignore. break; default: Log.e(TAG, "Couldn't get credential from result." + e.getLocalizedMessage()); break; } } break; } } @Override public void onBackPressed() { super.onBackPressed(); oneTapClient.signOut(); } }
그 다음은 가이드에 나와있는 코드 일부를 뽑아서 나의 코드에 넣기... 설명은 oneTapClient 을 생성하고 signInRequest 을 build하고 oneTapClient.beginSignIn 을 통해서 호출하면 실행이 된다는 것이다. 여기서 발견한 것은 가이드에 오타(?)가 있는 것만 같은 생각이 들었다.
가이드 발췌
가이드에서는 oneTapClient.beginSignIn( signUpRequest ) 라고 되어 있으나, 코틀린 예제는 다르다. 아마도 오타(?)일 것 같은 생각이 든다. 뭐 암튼... 실행해 보면 다음 처럼 볼 수 있다.
실행해본 예시화면
앱을 실행해 보면 구글 One Tap 로그인을 할 수 있는 화면이 실행이 되었다. 다만, 저걸 클릭후 오류가 발생했는데...
Couldn't get credential from result.10: Developer console is not set up correctly 이런 오류를 만나게 되었다면, 위에서 설명한 것 처럼 웹 어플용 ID 을 설정해야 한다.
다른 메시지는 16: Cannot find a matching credential. 이런걸 만날 수 있다. 이 경우는 clound console 에 OAuth 2.0 클라이언트 ID가 등록 되지 않은 경우에 만날 수 있다. 또는 다음 링크와 같은 경우에도 만날 수 있다.
오늘은 갑짜기 날이 많이 추워졌습니다. 이런 날은 방구성에 앉아서 날이 따스해지길 기다리는 것이 제일 일 것 같은데, 그냥 지난번 어딘가에서 보았던 차박 이야기가 생각이 나서, 핫팩을 찾아 보게 되었습니다. wiki 에서 말하는 핫팩은 이런 거 였어요.
--- 내용 퍼오기 ---
부직포 주머니 안에 쇳가루와촉매[1]가 들어있어서, 포장비닐을 뜯어서 흔들면철이 촉매와 혼합,산화되면서열이 발생한다.[2]부피가 작아서 주머니에 넣고 다니는 형태 뿐 아니라, 속옷에 붙이거나 신발에 깔거나, 겨울철 생물배송(주로 수생생물)을 할 때 집어넣거나 하는 다양한 형태의 변용이 가능하다.일회용이라는 게[3]역시 장점이자 단점. 오래 만지작거리다보면 부직포에서 미세한 산화철 입자들이 새어 나오기 때문에 손에서 쇠냄새가 난다.
산소와 반응하는 철가루라는 특성을 이용해,생존주의자들이 곡물을 장기보관하는데 산소제거용으로 쓰는 경우도 있다. 포대 안에 핫팩 하나 까넣고 최대한 공기를 뺀 뒤 밀봉하면 산소를 제거하는 것으로, 동일원리/목적으로 냉동피자나 비엔나 포장지 안에 보존제로 자그맣게 들어있는 경우도 있다.
핫팩의 원리를 응용하여 여러번 사용하는 편법도 있는데, 공기가 안통하도록 비닐로 꼭 묶어두면 금세 식어버린다. 비닐을 뜯으면 다시 산화하면서 발열.
---
어떻게 보면 작은 물건이기는 하나, 차박 하다가 일산화탄소 중독 사고가 나는 걸 보면서 그것 보다는 이것이 낮지 않나 하는 생각이 듭니다. 다만, 개인적으로 핫팩은 사용하고나서 잘 버려야 할 것 같은 생각이 들었습니다.
그래서 구글에게 물어 봤습니다. 여러가지 답이 있기는 하지만, 아무래도 재활용이 불가능해 보이니, 일반쓰레기로 배출해야 할 것 같습니다. 그안에 철가루가 들어 있다고 하니, 잘 버려야 후손들에게도 민폐가 되지 않을 것 같아요.
#스하리1000명프로젝트, A veces es difícil hablar con trabajadores extranjeros, ¿verdad? ¡Hice una aplicación sencilla que ayuda! Escribes en tu idioma y los demás lo ven en el suyo. Se traduce automáticamente según la configuración. Súper útil para chatear fácilmente. ¡Echa un vistazo cuando tengas la oportunidad! https://play.google.com/store/apps/details?id=com.billcoreatech.multichat416