안드로이드 알림의 보안 설정 UX 문제

언제부터인지 정확히는 모르겠지만 안드로이드엔 잠금화면에서 뜨는 알림들의 민감한 내용을 숨길 수 있는 설정이 있다. 민감하고 아니고의 기준은 알림의 내용과 관련이 없고 어떤 앱이 알림을 보냈느냐 뿐이다. 예를 들면 SMS나 메신저로 오가는 내용은 잠금화면에 떠버리면 개인정보나 프라이버시에 문제가 생길 수 있으니 어떤 앱에서 알림이 떴는지 아이콘만 표시하고 내용은 숨겨버리는 것이다. 이런 류의 다른 설정들처럼 전체 기본 설정이 있고 앱에 따라 개별 설정을 따로 할 수 있다. 예를 들자면 기본적으로는 알림 내용을 다 보이게 해 두고 메신저류는 앞에서 말한 이유로 내용을 숨기는 식이다.

하지만 이 설정에서 굉장히 잘못 된 점이 있는데 그 반대로 하는 게 아예 안 된다. 기본적으로는 모든 알림 내용을 숨기고 음악 재생 앱 등의 알림은 잠금화면에서도 보이게 하고 싶은데 이게 불가능하다. 알림 내용을 숨기는 걸 기본 설정으로 하면 앱 개별 설정에서는 알림 내용 전체를 보여주는 옵션 자체가 없어져버린다. 왜 그렇게 설계했는지는 구글의 마음이겠지만 보안적으로 이쪽 방식이 더 올바른 것인데 그걸 불가능하게 만들었다는 게 이해가 전혀 되질 않는다. 내가 원하는 방식으로 알림을 설정하려면 현재 상황에선 다음과 같은 과정을 거쳐야 한다.

  1. 기본 설정에선 모든 알림 내용 표시로 설정
  2. 내가 알림을 보고자 하는 앱(음악 앱)을 제외한 100~200여개의 앱 개별설정을 “잠금화면에서 내용 숨김”으로 바꾼다.
  3. 새로 앱을 설치한 경우 절대로 까먹지 말고 새 앱의 개별 알림 설정을 바꾼다.

2번까지는 귀찮지만 어떻게든 할 수 있는데 인간적으로 3번은 불가능하다고 봐야 한다. 반면 개별 설정의 제한이 풀린다면 다음과 같이 하면 된다.

  1. 기본 설정을 “잠금화면에서 내용 숨김”으로 변경
  2. 내가 원하는 앱(음악 앱 등) 몇 개만 잠금화면에서 내용 표시로 변경
  3. There is no 3

난 정말로 구글이 왜 이런 식으로 보안상 좋지도 않은 UX를 강요하는지 전혀 모르겠다.

안드로이드에서 기본 DB를 내장하기

개인적으로 노래방 검색 앱을 만들어 쓰고 있는데 데이터가 몇만 개가 되다보니 아무리 서버에서 JSON 형식으로 빠르게 준다고 해도 그걸 가지고 로컬에 DB를 구축하는 게 꽤나 오래 걸렸다. 그래서 기본 DB를 내장한 채 출시하고 업데이트만 새로 받도록 하면 어떨까 했다.

방법은 꽤나 간단한데 asset(혹은 res.raw)로 두고 DB를 열기 전 데이터베이스 디렉터리 안으로 복사를 해 주면 된다. dbHelper.get(Readable|Writable)Database()를 호출하기 전에는 DB를 열지 않으므로 db helper의 onCreate에서 미리 DB 파일이 존재하는지 확인한 후 없으면 파일을 통채로 복사해 주면 된다.

        public DbHelper(Context context) {
            super(context, DB_NAME, null, DB_VERSION);
            this.context = context;

            if (checkDbExists() == false) {
                try {
                    copyDatabase(DB_NAME);
                    Log.i("DB", "Copy initial database succeeded.");
                } catch (IOException e) {
                    Log.e("DB", "Failed to copy database");
                }
            }
        }

        private void copyDatabase(String filename) throws IOException {
            InputStream input = context.getAssets().open(DB_NAME);
            context.getDatabasePath(filename).getParentFile().mkdirs();
            String dbPath = context.getDatabasePath(filename).toString();
            OutputStream output;
            output = new FileOutputStream(dbPath);

            byte[] buffer = new byte[1024];
            int length;
            while ((length = input.read(buffer)) > 0) {
                output.write(buffer, 0, length);
            }

            output.flush();
            output.close();
            input.close();
        }

또 다른 문제가 하나 발생했는데 간편검색을 위한 컬럼을 추가하면서 초기 DB파일이 많이 바뀌었다. 신규 사용자는 역시 복사가 되겠지만 기존 사용자들은 DB가 이미 있기 때문에 파일을 복사하지 않을 것이고 몇만 개의 데이터에 대해 무거운 작업을 처리해야 했다. 다른 DB 파일에서 테이블 하나만 복사를 할 수 없나 싶다가 방법을 찾았다. attach 명령으로 다른 파일을 불러온 후 insert ~ select ~ from ~으로 데이터를 복사하면 금방 끝났다.
단, 그냥 insert를 하면 중복된 행이 발생하니 unique (col1, col2) on conflict update와 같은 스키마를 입혀 준 상태로 해야 한다.

여기서도 문제가 있는데 onUpgrade 메소드는 트랜잭션이 걸린 상태의 db를 인자로 전달해 준다. 하지만 attach 명령은 트랜잭션 중간엔 쓰지 못한다고 에러가 뜨는데 그래서 트랜잭션을 임시로 끝낸 후 작업을 한 뒤 다시 트랜잭션을 시작해 줘야 한다(사실 이 방법도 좋은 방법은 아니다. 트랜잭션이 왜 있는가).

                    db.execSQL(
                            "alter table " + TABLE_SONG + " add column " + COL_SIMPLIFIED +
                                    " not null default \"\";"
                    );

                    try {
                        // Temporarily end transaction to attach database;
                        db.setTransactionSuccessful();
                        db.endTransaction();

                        String dbName = "karaoke_orig";
                        copyDatabase(dbName);
                        db.execSQL(MessageFormat.format("attach \"{0}\" as orig",
                                context.getDatabasePath(dbName).toString()));
                        db.execSQL("insert into songs select * from orig.songs");
                        db.execSQL("detach orig");
                        Log.d("MIGRATE", "Copy simplified from asset");
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        db.beginTransaction();
                    }

Android 디버그 빌드시 앱 아이콘이나 이름 바꾸기

예전에 안드로이드 디버그 빌드시 스토어 버전과 충돌하지 않도록 버전네임과 패키지네임을 바꾸도록 설정하는 글을 쓴 적이 있다.
하지만 이런 방법을 써도 나중에 앱을 실행할 때 런처가 두 개나 존재하는데 이름도 똑같고 아이콘마저 똑같아서 어떤 게 디버그 버전인지 구분이 가지 않는 문제가 있었고 아이콘이든 이름이든 바꿀 수 있으면 좋겠다고 생각했었다.

예전엔 못 찾았지만 오늘 스택오버플로를 뒤져보다가 방법을 찾았는데 번역된 리소스를 넣을 때 values-ko 같은 다른 이름의 디렉터리를 만드는 것과 비슷하게 src 디렉터리 밑에 main 대신 debug를 만들어 똑같은 구조를 만들어 두면 디버그 빌드시 해당 디렉터리 안의 리소스를 사용하게 된다. 나는 새로 이미지를 만들기 귀찮아서 런처의 이름만 바꿨지만 리소스든 뭐든 얼마든지 바꿀 수 있으니 디버그모드에만 보이는 기능을 추가할 수도 있겠다.

screenshot_20161204-215933