안드로이드 앱을 Google Play Store에 출시하려다 개인정보처리방침 문제로 심사가 거절되셨나요? 저희 팀도 최근 비슷한 경험을 했습니다. AI 기반 음식 사진 분석 앱 '식사진'을 개발하고 심사를 신청했다가 거절당했던 경험과 해결 과정을 공유하고자 합니다.
문제 상황 소개
앱 소개: 식사진
'식사진'은 사용자가 찍은 음식 사진을 ChatGPT API를 활용해 분석하여 칼로리와 영양 정보를 제공하는 안드로이드 앱입니다. 사진 촬영과 갤러리 접근 권한, 그리고 OpenAI API를 통한 데이터 처리가 필요한 앱이었죠.
받은 거절 사유
Google Play Console에서 받은 거절 메시지는 다음과 같았습니다:
"귀하의 앱이 개인정보처리방침을 필요로 하는 민감한 사용자 데이터를 수집/전송하고 있으나, 유효한 개인정보처리방침이 제공되지 않았습니다."
거절 원인 분석
Google Play의 개인정보 정책 요구사항
Google Play는 다음과 같은 경우 반드시 개인정보처리방침을 요구합니다:
- 카메라, 마이크 등 민감한 권한 사용
- 개인 식별 가능 정보 수집
- 제3자 서비스(이 경우 OpenAI API) 사용
우리 앱의 경우 세 가지 모두에 해당했습니다.
부족했던 점
- 개인정보처리방침 URL만 등록하고 상세 내용이 미흡했음
- 앱 내 권한 요청 시 사용 목적 미설명
- OpenAI API 데이터 처리 관련 고지 부재
해결 과정
개인정보처리방침 작성
먼저 다음 요소들을 포함한 개인정보처리방침을 작성했습니다:
- 수집하는 개인정보 항목
- 카메라로 촬영한 음식 사진
- 갤러리에서 선택한 사진
- 기기 식별자
- 수집 목적
- 음식 사진 분석 서비스 제공
- 앱 성능 개선
- 사용자 경험 개선
- 제3자 제공 정보
- OpenAI API 사용 목적과 전송되는 데이터 범위
- 데이터 보관 기간과 파기 정책
앱 코드 수정
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
permission_handler: ^10.2.0
image_picker: ^1.0.4
권한 요청 부분의 구현입니다
import 'package:permission_handler/permission_handler.dart';
import 'package:flutter/material.dart';
class CameraPermissionHandler {
Future<bool> requestCameraPermission(BuildContext context) async {
final status = await Permission.camera.status;
if (status.isGranted) {
return true;
}
if (status.isDenied) {
if (await _showPermissionContextDialog(context)) {
final result = await Permission.camera.request();
return result.isGranted;
}
return false;
}
if (status.isPermanentlyDenied) {
await _showSettingsDialog(context);
return false;
}
return false;
}
Future<bool> _showPermissionContextDialog(BuildContext context) async {
final result = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('카메라 권한이 필요합니다'),
content: const Text(
'음식 사진을 촬영하여 영양 정보를 분석하기 위해 카메라 접근 권한이 필요합니다.'
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('거절'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('권한 설정하기'),
),
],
),
);
return result ?? false;
}
Future<void> _showSettingsDialog(BuildContext context) async {
await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('권한 설정'),
content: const Text(
'카메라 기능을 사용하기 위해서는 설정에서 권한을 허용해주세요.'
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('취소'),
),
TextButton(
onPressed: () {
openAppSettings();
Navigator.pop(context);
},
child: const Text('설정으로 이동'),
),
],
),
);
}
}
동의 화면 구현
앱 최초 실행 시 개인정보 수집 동의를 받는 화면을 추가했습니다:
import 'package:flutter/material.dart';
import 'package:shared_preferences.dart';
class PrivacyConsentScreen extends StatefulWidget {
const PrivacyConsentScreen({Key? key}) : super(key: key);
@override
State<PrivacyConsentScreen> createState() => _PrivacyConsentScreenState();
}
class _PrivacyConsentScreenState extends State<PrivacyConsentScreen> {
bool _isConsentChecked = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('개인정보 수집 동의'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
'개인정보 수집 및 이용 동의',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
const Text(
'식사진 서비스 이용을 위해 다음과 같이 개인정보를 수집 및 이용합니다:',
style: TextStyle(fontSize: 16),
),
const SizedBox(height: 16),
_buildPrivacyInfoCard(),
const SizedBox(height: 24),
_buildConsentCheckbox(),
const SizedBox(height: 16),
_buildPrivacyPolicyLink(),
const Spacer(),
ElevatedButton(
onPressed: _isConsentChecked ? _handleConsent : null,
child: const Text('동의하고 계속하기'),
),
],
),
),
);
}
Widget _buildPrivacyInfoCard() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text('• 수집항목: 카메라로 촬영한 음식 사진, 갤러리 사진, 기기 식별자'),
SizedBox(height: 8),
Text('• 수집목적: 음식 사진 분석 서비스 제공, 앱 성능 개선'),
SizedBox(height: 8),
Text('• 보관기간: 서비스 이용 종료 시까지'),
],
),
),
);
}
Widget _buildConsentCheckbox() {
return CheckboxListTile(
value: _isConsentChecked,
onChanged: (value) {
setState(() {
_isConsentChecked = value ?? false;
});
},
title: const Text('개인정보 수집 및 이용에 동의합니다'),
controlAffinity: ListTileControlAffinity.leading,
);
}
Widget _buildPrivacyPolicyLink() {
return GestureDetector(
onTap: _openPrivacyPolicy,
child: const Text(
'개인정보처리방침 전문 보기',
style: TextStyle(
color: Colors.blue,
decoration: TextDecoration.underline,
),
),
);
}
void _openPrivacyPolicy() async {
// URL 런처를 사용하여 개인정보처리방침 페이지 열기
const url = 'https://your-privacy-policy-url.com';
// url_launcher 패키지를 사용하여 구현
}
Future<void> _handleConsent() async {
// SharedPreferences를 사용하여 동의 여부 저장
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('privacy_consent', true);
// 메인 화면으로 이동
if (mounted) {
Navigator.of(context).pushReplacementNamed('/home');
}
}
}
성공적인 심사 통과
위와 같은 수정 사항을 적용한 후 재심사를 신청했고, 약 3일 후 승인을 받을 수 있었습니다. 특히 다음 사항들이 중요했습니다:
- 명확한 개인정보처리방침
- 사용자 동의 절차 구현
- 권한 요청 시 상세한 설명 제공
- 제3자 서비스 사용에 대한 투명한 고지
다른 개발자들을 위한 조언
- 앱 출시 전 개인정보처리방침을 미리 준비하세요
- 권한 요청 시 사용자 친화적인 설명을 포함하세요
- 제3자 API 사용 시 관련 정책을 꼼꼼히 확인하세요
- 사용자 데이터 처리 과정을 투명하게 공개하세요
이런 경험을 통해 개인정보보호의 중요성을 다시 한번 실감했습니다. 앱 개발에서 기능만큼이나 중요한 것이 사용자의 개인정보를 안전하게 보호하는 것임을 배웠습니다.
참고 자료
이 글이 Google Play 심사 과정에서 비슷한 어려움을 겪고 계신 다른 개발자분들께 도움이 되길 바랍니다.
https://pointer81.tistory.com/entry/android-kakao-login-keyhash-app-signing-guide
'Develop' 카테고리의 다른 글
2024 주니어 백엔드 개발자 면담: 현장에서 듣는 진짜 고민 (1) | 2024.12.18 |
---|---|
식사진 - 개인정보처리방침 (0) | 2024.12.17 |
Android 앱의 카카오 로그인 문제 해결하기 - 키해시와 앱 서명의 이해 (0) | 2024.12.12 |
MealLens 앱 소개: AI 기반 음식 분석 도우미 (1) | 2024.12.10 |
2024년 업무 효율을 높여줄 크롬 확장 프로그램 완벽 가이드 (0) | 2024.12.07 |