2026/02/25

오늘의 이야기

어느날 받은 메일함이 기억나서 오늘도 나는 admob center 을 열어 보았다. ㅠㅠ;; 아니나 다를까 또 다른앱의 정책 위반 이야기가 나와 있다.

admob 정책 위반


이번엔 무엇 때문에 이런일이 생길것인가 ??? 내용은 뭐 말 그대로

1. 의도하지 않은 광고 유도
2. 광고프레임 크기 변형

1번의 경우는 어떤때 발생하는 가 ? 아마도 변형된 Toast 때문에 발생한다고 보인다. 앱을 종료할 떄 알림을 주기 위해서 Toast 을 변형하고 그 안에 광고를 넣었다. 그랬더니 앱이 종료를 시도 할 떄 Toast 가 노출 되지만, 실제 앱이 종료된 이후에도 잔상(?)이 남아 있게 되면, 그것을 의도하지 않은 광고 유도 라고 판단하는 것 같다.

2번의 경우는 광고 프레임 변형 이라고 하는데, 그건 아마도 layout 배치를 하는 과정에서 banner 광고를 사용하고 있는데, banner 광고의 전체가 노출 되지 못하도록 layout 을 구성해서 그런것으로 이해가 된다.

그런걸 정리해야 하는 시기가 된 것이다.

그래서 오늘도 난 ... 밤을 보내고 있다. ㅋ~





오늘의 이야기

https://developers.google.com/identity/one-tap/android/get-started


그간은 firebase 에서 지원하는 구글 로그인만 보고 있었는데, 오늘은 은인(?)을 만나게 되어 다른거 하나를 알게 되었다.
구글에서 말하는 로그인 / 가입 방식... 이걸 이용하면 웹 사이트 운영시에는 도움이 될 것 같기도 하다.


 


2024.10.16 다시 찾아 본 글에서 이제는 더 이상 one tap login 을 사용할 수 없을 듯 합니다.


 



저 자료를 보면서 구현을 해 보았다. 제일 먼저 구현할 부분은 gradle 파일에 설정하기


dependencies { ... implementation 'com.google.android.gms:play-services-auth:20.0.1' }

가이드에 나와있는데로... 그 다음은 API 활용을 위해서 cloud 에 사용자 인증정보를 추가 하고 클라이언트ID을 받아서 챙겨 두자. 등록할 때 앱의 package 이름 그리고 SHA-1 디지털지문이 필요하므로 이것도 반드시 기억.

여기서 주의할 부분을 찾았다. 가이드에 나와 있는 부분인데, 간과하고 넘어갔더니.


Google API 콘솔 프로젝트 설정



  1. 에서 프로젝트 열기 API 콘솔을 , 또는 당신이 아직없는 경우 프로젝트를 만듭니다.

  2. OAuth 동의 화면 페이지에서 모든 정보가 완전하고 정확한지 확인하십시오. 특히 앱의 개인정보 보호정책 및 서비스 약관의 URL을 지정했는지 확인하세요.

  3. 자격 증명 페이지에서 앱에 대한 Android 클라이언트 ID가 아직 없는 경우 생성합니다. 앱의 패키지 이름과 SHA-1 서명을 지정해야 합니다.

  4. 자격 증명 페이지에서 웹 애플리케이션 클라이언트 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가 등록 되지 않은 경우에 만날 수 있다.
또는 다음 링크와 같은 경우에도 만날 수 있다.

https://stackoverflow.com/questions/61768804/getting-16-cannot-find-a-matching-credential-when-doing-one-tap-sign-in-and-s



 


Getting "16: Cannot find a matching credential" when doing One Tap sign-in and sign-up on Android


I have been following the guide for setting up One Tap sign-in and sign-up on Android, and have used the example code to set up the One Tap client, but every time I call oneTapClient.beginSignIn(


stackoverflow.com




 





오늘의 이야기


#스하리1000명프로젝트,
迷失在韩国?即使您不会说韩语,这个应用程序也可以帮助您轻松出行。
只需说出您的语言即可 - 它会翻译、搜索并以您的语言显示结果。
非常适合旅行者!支持英语、日语、中文、越南语等10多种语言。
现在就试试吧!
https://play.google.com/store/apps/details?id=com.billcoreatech.opdgang1127




2026/02/24

오늘의 이야기

그것도 종이로 말이에요.


ㅋㅋㅋ

이런일이 아직 갈길이 멀어요...






오늘의 이야기

https://developer.android.com/courses/pathways/android-basics-kotlin-four



 


Add a button to an app  |  Android Basics in Kotlin - Intro - Add a button  |  Android Developers


Learn more concepts in Kotlin—including classes, objects, and conditionals—to create an interactive app for your users.


developer.android.com




앱을 하나 만들어 보았다. Kotlin 으로 하는 첫번째 프로젝트. 이 걸 해 보는 이유는 java 만으로 하는 앱은 많이 해 보았는데, kotlin 으로 하는 코딩은 처음인지라, 배워보고자 해서 developer 에서 코트랩을 살펴보았다.


 


ㅋ 구현되는 모습은 어떨까 ?


 



주사위 동작


동작은 그냥 하는 주사위 모양이다.  동작하는 기능 구현은 아래 소스를 보는 도움이 될 것 같다. 


import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.billcoreatech.kotlinexam0115.databinding.ActivityMain2Binding

class MainActivity2 : AppCompatActivity() {
var TAG: String = "MainActivity2";

lateinit var binding: ActivityMain2Binding ;

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMain2Binding.inflate(layoutInflater)
setContentView(binding.root)

binding.button.setOnClickListener{
Log.e(TAG, "");
Toast.makeText(this, rollDice(), Toast.LENGTH_SHORT).show()

}
}

private fun rollDice(): String {
val dice = Dice(6)
val diceRoll = dice.roll()
binding.textView3.setText(diceRoll.toString())
when(diceRoll) {
1 -> binding.imageView2.setImageResource(R.drawable.dice_1)
2 -> binding.imageView2.setImageResource(R.drawable.dice_2)
3 -> binding.imageView2.setImageResource(R.drawable.dice_3)
4 -> binding.imageView2.setImageResource(R.drawable.dice_4)
5 -> binding.imageView2.setImageResource(R.drawable.dice_5)
6 -> binding.imageView2.setImageResource(R.drawable.dice_6)
}
return diceRoll.toString()
}
}

 


이게 java 로 구현이 되면 아래와 같이 이렇게 구현이 될 것 같다.


import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import com.billcoreatech.kotlinexam0115.databinding.ActivityMain2Binding;

public class MainActivity3 extends AppCompatActivity {

ActivityMain2Binding binding ;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMain2Binding.inflate(getLayoutInflater());
setContentView(binding.getRoot());

binding.button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(getApplicationContext(), rollDice() , Toast.LENGTH_SHORT).show();
}
});
}

private String rollDice() {
Dice dice = new Dice(6);
int iDice = dice.roll() ;
binding.textView3.setText(String.valueOf(iDice));
switch (iDice) {
case 1: binding.imageView2.setImageResource(R.drawable.dice_1); break ;
case 2: binding.imageView2.setImageResource(R.drawable.dice_2); break ;
case 3: binding.imageView2.setImageResource(R.drawable.dice_3); break ;
case 4: binding.imageView2.setImageResource(R.drawable.dice_4); break ;
case 5: binding.imageView2.setImageResource(R.drawable.dice_5); break ;
case 6: binding.imageView2.setImageResource(R.drawable.dice_6); break ;
}

return String.valueOf(iDice) ;
}

}

아직은 어떤게 더 코드 작업이 수월하지는 잘 모르겠다.  이런 걸 여러번 하다 보면,  알게 될려는지...  layout 은 그대로 하나 만들어서 했고,  구현하는 부분만 kotlin 으로, java 로 만들어 보았다.


 


어떻게 달라질지...





오늘의 이야기



#스치니1000프로젝트 #재미 #행운기원 #Compose #Firebase

🎯 야 너 토요일마다 로또 확인하냐?
나도 맨날 "혹시나~" 하면서 봤거든 ㅋㅋ

근데 이제는 그냥 안 해
AI한테 맡겼어 🤖✨

그것도 구글 Gemini로다가!

그래서 앱 하나 만들었지
👉 "로또 예상번호 by Gemini" 🎱

AI가 분석해서 번호 딱! 뽑아줌
그냥 보고 참고만 하면 됨

재미로 해도 좋고…
혹시 모르는 거잖아? 😏


https://play.google.com/store/apps/details?id=com.billcorea.gptlotto1127




오늘의 이야기

https://www.raspberrypi.com/products/raspberry-pi-high-quality-camera/



 


Buy a Raspberry Pi High Quality Camera – Raspberry Pi


12.3 megapixel Sony IMX477 sensor, 7.9mm diagonal image size, and back-illuminated sensor architecture, with adjustable back focus and support for C- and CS-mount lenses


www.raspberrypi.com




 


라즈베리파이... 그것의 활용 가능성은 어디까지 인가?


음... 지금은 그저 그냥 우리 집 반려 거북이 shot을 찍어 인스타에 게시하는 정도의 활용을 하고 있다.  다음에 하고 싶은 건 하늘을 찍어 올려보는 것이다. 


 


지금 쓰고 있는 카메라 모듈은 그렇게 좋은 화질을 제공하지 못한다. 그래서 좀 아쉽다. 어느 날 보니 메일에 이런 게 있었다. 고화질 카메라...  그런데 좀 가격이 나온다. 모듈과 렌즈를 따로 구매를 해야 하는 것 같다.


 


https://www.devicemart.co.kr/goods/view?no=12543579&src=raspberrypi 



 


라즈베리파이 HQ 카메라모듈 12.3MP (Raspberry Pi High Quality Camera)


12.3 메가픽셀 고해상도 Sony IMX477 센서 / 기존 IMX219 대비 저조도 퍼포먼스 향상 / 백포커스, C 마운트 및 CS 마운트 렌즈 지원


www.devicemart.co.kr




 


https://www.devicemart.co.kr/goods/view?no=12543581 



 


라즈베리파이 HQ 카메라모듈용 16mm 망원 렌즈 10MP (16mm Telephoto Lens for Raspberry Pi High Quality Camera)


라즈베리파이 HQ 카메라 모듈 전용 16mm 10MP 망원 렌즈 / 멀리 있는 물체를 Zoom으로 당겨서 촬영하는 프로젝트에 적합한 렌즈 / 렌즈 보호용 덮개 포함


www.devicemart.co.kr




 


두 개를 다 사려면... 13만 원 정도... 취미 생활하기도 힘드네 (?) ㅠㅠ;;


 


뭐 아무튼 준비를 해 봐야겠다.  좋은 정보인 것 같다.





오늘의 이야기


#스하리1000명프로젝트

스치니들!
내가 만든 이 앱은, 내 폰에 오는 알림 중에서 중요한 키워드가 있는 경우
등록해둔 친구에게 자동으로 전달해주는 앱이야 📲

예를 들어, 카드 결제 알림을 와이프나 자녀에게 보내주거나
이번 달 지출을 달력처럼 확인할 수도 있어!

앱을 함께 쓰려면 친구도 설치 & 로그인해줘야 해.
그래야 친구 목록에서 서로 선택할 수 있으니까~
서로 써보고 불편한 점 있으면 알려줘 🙏

👉 https://play.google.com/store/apps/details?id=com.nari.notify2kakao





오늘의 이야기

몇일쨰 -10도를 오가는 날씨가 이어지고 있습니다. 날이 많이 춥네요. 날이 추우는 따듯한 무언가가 필요하 시점인 것 같아요. 그래서 오늘은 바라클라바를 찾아 보았어요.


 


바라클라바 사전적 의미



초록창이 알려주는 의미는 머리에서 어깨까지 내려오는 대형 모자라고 하네요.  예전에는 귀막이 하나만 있어도 따스겠다고 생각을 했었는데,  이런 것도 있었네요.   


 


구글에서 찾은 이미지를 봐도 딱 알겠네요. 


 


바라클라바 이미지



상품광고 이미지를 퍼온거라, 조심 스럽기는 한데, 뭐... 돌아다니는 거니...  암튼 오늘은 추운 날씨에 필요할 것 같은 잇템 하나 찾아 보았습니다.


 





오늘의 이야기

인용 URL : https://namu.wiki/w/%ED%95%AB%ED%8C%A9


핫팩 이미지



오늘은 갑짜기 날이 많이 추워졌습니다.  이런 날은 방구성에 앉아서 날이 따스해지길 기다리는 것이 제일 일 것 같은데,  그냥 지난번 어딘가에서 보았던 차박 이야기가 생각이 나서, 핫팩을 찾아 보게 되었습니다. 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




오늘의 이야기

섭섭이네 호끌락한집(조그만집) 메뉴는 커리와 국수... 커리는 마침 1인본만 남았다 해서 고기국수 먹음... 그렇게 찾아볼 정도는 아닌 듯 정말 작음 테이블 4개(4인기준) https://goo.gl/maps/dkjMxHVEHtJSVWhN6 Googl...