2016년 2월 13일 토요일

ScreenLock 의 구현

휴대폰을 키자 마자 어떤 화면을 띄우게 만들고 싶을 때는
Intent.ACTION_SCREEN_OFF
를 받아들이는 BroadcastReceiver를 등록(registerReceiver)하면 된다.

얼핏 생각하기에
AndroidManifest.xml에

<receiver    
   android:name=".ScreenOffReceiver"    
   android:enabled="true"   
   android:exported="true">
    <intent-filter>       
        <action android:name="android.intent.action.ACTION_SCREEN_OFF"/>    
    </intent-filter>
</receiver>

위와 같이 등록하면 될 거라 생각할 수 있지만 안드로이드 프레임워크 내부적으로
몇 가지 필터링은 manifest에서 등록해서 사용할 수가 없다.

성능상의 문제때문에 그런 거 같다.

그래서 이를 우회적으로 구현을 해야만 한다.

AlarmManager를 통해서 Service를 반복적으로 실행시켜야 한다.
그리고 그 Service에서 동적으로 registerReceiver() 함수를 호출해서
ACTION_SREEN_OFF 에 대한 통지를 받고 호출되는 Receiver를 등록해준다.

얼만큼의 주기로 Service를 반복 호출할지는 개발자가 정해야 하는데,
메모리를 자주 정리하며 스맛폰을 사용하는 사용자가 아닌 이상
그리 자주 할 필요는 없다.

Service가 호출이 되어야 하는 시점은
우리의 앱이 kill 이 되었을 때이다.
kill 되지 않았으면 굳이 호출하지 않아도 크게 문제가 없다.

예를 들어 5초 주기로 호출한다면 앱을 아무리 수동으로 kill 하고 바로
화면을 껐다 켜도 대부분 원하는 화면이 바로 뜨는 것을 확인할 수가 있다.

어쨌든 구현 순서는 다음과 같다.

AlarmManager 을 통해서  Service를 호출하는 Receiver를 호출하도록 한다.



AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);

Intent myIntent = new Intent(MainActivity.this, RegisterReceiverServiceAlamReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(MainActivity.this, 0, myIntent, 0);

long period = 1000 * 5;
long after = 1000 * 5;
long t = SystemClock.elapsedRealtime();
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, t + after, period, pendingIntent);


RegisterReceiverServiceAlamReceiver 는 AlarmManager에 의해 호출되어
RegisterReceiverService를 호출할 뿐이다.


public class RegisterReceiverServiceAlamReceiver extends BroadcastReceiver {

    public RegisterReceiverServiceAlamReceiver() {
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        Intent service1 = new Intent(context, RegisterReceiverService.class);
        context.startService(service1);
    }
}

RegisterReceiverService 는 실제 SCREEN_OFF를 BroadCast받아서 Activity를 띄워주는
녀석을
registerReceiver  해준다.


public class RegisterReceiverService extends Service {
    public RegisterReceiverService() {
    }

    private ScreenOffReceiver mReceiver = null;

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();

        mReceiver = new ScreenOffReceiver();
        IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
        registerReceiver(mReceiver, filter);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId){
        super.onStartCommand(intent, flags, startId);

        if(intent != null){
            if(intent.getAction()==null){
                if(mReceiver==null) {
                    mReceiver = new ScreenOffReceiver();
                    IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
                    registerReceiver(mReceiver, filter);
                }
            }
        }
        return START_REDELIVER_INTENT;
    }

    @Override
    public void onDestroy(){
        super.onDestroy();

        if(mReceiver != null){
            unregisterReceiver(mReceiver);
        }
    }
}



ScreenOffReceiver 는 실제로 SCREEN_OFF 를 받아서 Activity 를 띄워주는 BroadCastReceiver이다.


public class ScreenOffReceiver extends BroadcastReceiver {
    private TelephonyManager telephonyManager = null;
    private boolean isPhoneIdle = true;

    public ScreenOffReceiver() {
    }

    @Override
    public void onReceive(Context context, Intent intent) {

        if(telephonyManager == null){
            telephonyManager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
            telephonyManager.listen(phoneListener, PhoneStateListener.LISTEN_CALL_STATE);
        }

        if(isPhoneIdle){
            if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
                Intent i = new Intent(context, ScreenOnActivity.class);
                i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                context.startActivity(i);
            }
        }
    }

    private PhoneStateListener phoneListener = new PhoneStateListener(){
        @Override
        public void onCallStateChanged(int state, String incomingNumber){
            switch(state){
                case TelephonyManager.CALL_STATE_IDLE :
                    isPhoneIdle = true;
                    break;
                case TelephonyManager.CALL_STATE_RINGING :
                    isPhoneIdle = false;
                    break;
                case TelephonyManager.CALL_STATE_OFFHOOK :
                    isPhoneIdle = false;
                    break;
            }
        }
    };
}

여기서 다소 주의할 점은 전화 받는 상황에서 화면이 꺼지는 경우에는 동작하지 않도록
isPhoneIdle이라는 flag 로 구분하였다.

이상의 기능을 구현하기 위해 총 2개의 permission 을 더해줘야 한다.

<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />

댓글 없음:

댓글 쓰기