2016년 9월 29일 목요일

[안드로이드] 7.0(누가) Quick Settings 타일 API 적용 제목 수정

Quick Settings?

  • 안드로이드 상단을 스와이프하면 보이는 스위치 버튼들
  • 기존 안드로이드 버전에서는 커스텀 버튼을 만들 수 없었다.(제조사에서 만든 버튼 제외)
  • 7.0부터는 일반 앱에서 제공하는 커스텀 버튼을 추가할 수 있다.
  • 이 버튼은 앱을 시작하기 위한 용도가 아니고, 앱을 켜지 않고도 빠르게 on/off해야하는 기능을 추가하는 용도로 사용되어야 한다.
  • Quick Settings를 구현하기 위해서는 7.0(API 24)에서 추가된 Tile API를 이용해야 한다.
Tile

Quick Settings 구현

TileService 추가

  • Service클래스의 하위 클래스로 Quick Setting을 구현하기 위한 컴포넌트 클래스
  • 매니페스트 파일에 TileService를 등록해야 한다.
    • android:label 속성값은 타일 버튼의 설명이 들어간다. 코드로 변경가능하다.
    • android:icon 속성값은 타일 버튼의 아이콘이 들어간다. 코드로 변경가능하다. (아이콘은 안드로이드 정책에 따라 반드시 흰색이어야 한다. off, 비활성 표시를 위해 알파를 이용한다)
    • android.permission.BIND_QUICK_SETTINGS_TILE 퍼미션을 꼭 추가해주어야 한다.
    • 시스템이 TileService를 인식하려면 TileService.ACTION_QS_TILE 액션을 인텐트필터로 등록해야 한다.
<service android:name=".TestTileService"
            android:label="Test Label"
            android:icon="@drawable/ic_toastdrive"
            android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
            <intent-filter>
                <action android:name="android.service.quicksettings.action.QS_TILE" />
            </intent-filter>
        </service>
  • 시스템은 모든 앱의 매니페스트를 읽어서, ACTION_QS_TILE 액션을 추가한 TileService를 타일 리스트에 추가한다.(아직은 사용자의 입력을 받지 못한다.)
Tile
  • 타일 리스트에 있는 타일을 QuickSetting 영역으로 드래그하면, 이때부터 타일버튼으로 사용자의 입력을 받을 수 있다.
Tile

Tile 버튼으로 사용자의 입력 받기

  • TileService는 버튼의 상태에 따라 콜백을 받는 다음 메서드들이 정의되어 있다.
    • onTileAdded() : 타일이 QuickSetting 영역에 추가되었을 때 호출
    • onTileRemoved() : 타일이 QuickSetting 영역에서 제거되었을 때 호출
    • onStartListening() : 타일이 리스닝 상태로 들어갔을 때(QuickSetting 영역이 보일때) 호출
    • onStopListening() : 타일이 리스닝 상태에서 나왔을 때(QuickSetting 영역이 가려질때) 호출
    • onClick() : QuickSetting 영역에서 타일이 눌렸을 때 호출
  • onTileAdded/Removed, onStart/StopListening 메서드는 QuickSetting 영역과 타일리스트의 경계에서 여러번 호출될 수 있으니 유의하도록 한다.
  • 일반적으로 onClick()메서드를 이용해 사용자와 인터렉션 한다.
public class TestTileService extends TileService {
    boolean isListening = false;
    ...

    @Override
    public void onClick() {
        Tile tile = getQsTile();
        int tileState = tile.getState();
        if (isListening && tileState != Tile.STATE_UNAVAILABLE) {
            tile.setState(tileState == Tile.STATE_ACTIVE? Tile.STATE_INACTIVE : Tile.STATE_ACTIVE);
            tile.updateTile();
        }
    }
    ...
}
  • 앱의 생명주기와는 상관없이, Tile 객체는 상태를 보존한다. 그러나, 시스템이 재부팅되면 상태보존이 되지 않는다.(실장비에서 확인이 필요)

Tile 버튼을 활용하기 위한 TileService의 기능들

다이얼로그 보여주기

  • TitleService 클래스의 showDialog() 메서드를 호출한다. 인자로는 보여줄 다이얼로그 객체를 넘겨준다.
  • 이 메서드를 호출하면, Quick Setting 영역은 닫히고, 다이얼로그가 호출된다.
  • 다이얼로그는 반드시 Theme.AppCompat 테마 / 하위테마를 사용해야 한다.
public class TestTileService extends TileService {
    boolean isListening = false;
    ...

    @Override
    public void onClick() {
        Tile tile = getQsTile();
        int tileState = tile.getState();
        if (isListening && tileState != Tile.STATE_UNAVAILABLE) {
            tile.setState(tileState == Tile.STATE_ACTIVE? Tile.STATE_INACTIVE : Tile.STATE_ACTIVE);
            tile.updateTile();
            if (tileState == Tile.STATE_ACTIVE) {
                // 보여줄 다이얼로그 생성
                AlertDialog dialog = new AlertDialog.Builder(this, R.style.MaterialBaseTheme_AlertDialog)
                        .setTitle("Test Title")
                        .setMessage("Test Message")
                        .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                Toast.makeText(AppApplication.sContext, "OK", Toast.LENGTH_SHORT).show();
                            }
                        }).setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                Toast.makeText(AppApplication.sContext, "Cancel", Toast.LENGTH_SHORT).show();
                            }
                        }).create();
                // 다이얼로그 호출
                showDialog(dialog);
            }
        }
    }
    ...
}

엑티비티 시작하기

  • TileService 클래스의 startActivityAndCollapse() 메서드를 호출한다. 인자로는 엑티비티를 호출하기 위한 인텐트를 넘겨준다.
  • 이 메서드를 호출하면 Quick Setting 영역은 닫히고, 엑티비티가 실행된다.
  • 만약, 기존 앱이 실행중이라면 인자로 넘겨주는 Intent의 엑티비티 스택관련 플래그에 영향을 받는다.
public class TestTileService extends TileService {
    boolean isListening = false;
    ...

    @Override
    public void onClick() {
        Tile tile = getQsTile();
        int tileState = tile.getState();
        if (isListening && tileState != Tile.STATE_UNAVAILABLE) {
            tile.setState(tileState == Tile.STATE_ACTIVE? Tile.STATE_INACTIVE : Tile.STATE_ACTIVE);
            tile.updateTile();
            if (tileState == Tile.STATE_ACTIVE) {
                // 호출할 인텐트 생성
                Intent intent = new Intent(AppApplication.sContext, WebViewActivity.class);
                intent.putExtra(WebViewActivity.ARG_URL, "http://developer.android.com");
                // 엑티비티 호출
                startActivityAndCollapse(intent);
            }
        }
    }
    ...
}

잠금화면에서의 동작

  • 잠금화면 상태에서는 잠금화면에 가려서 위의 다이얼로그 / 엑티비티 호출은 보이지 않는다.(잠금화면에 가려졌을 뿐, 정상적으로 동작한다.)
  • 현재 상태가 잠금화면 상태인지를 확인하려면 다음 메서드를 이용한다.
    • isLocked() : 현재 상태가 잠금화면일 경우 true 반환
    • isSecure() : 현재 상태가 보안중일 경우 true 반환(true 인 경우에는 당연히 잠금화면 상태이므로 isLocked()는 true를 반환)
  • TileService 의 unlockAndRun() 메서드를 이용하면 잠금화면을 해제하고 동작을 수행한다. 인자로는 unlock을 수행한 후에 수행될 내용을 구현한 Runnable 객체이다.
    • unlockAndRun() 메서드가 Quick Setting 영역을 닫지는 않는다. 그래서 Quick Setting 영역이 그대로 보이며, 그 뒤에서 동작이 수행된다.
    • 패턴이 걸려있는 경우, unlockAndRun() 메서드가 호출되면 패턴해제화면이 노출된다. 패턴을 풀어야 Runnable 객체를 수행한다.(지문은 실 장비에서 테스트가 필요하지만, 동일한 프로세스일 것으로 추정)
    • 패턴해제화면에서 해제를 취소한 경우 Runnable 객체의 동작이 수행되지 않는다.
public class TestTileService extends TileService {
    boolean isListening = false;
    ...

    @Override
    public void onClick() {
        Tile tile = getQsTile();
        int tileState = tile.getState();
        if (isListening && tileState != Tile.STATE_UNAVAILABLE) {
            tile.setState(tileState == Tile.STATE_ACTIVE? Tile.STATE_INACTIVE : Tile.STATE_ACTIVE);
            tile.updateTile();
            if (tileState == Tile.STATE_ACTIVE) {
                unlockAndRun(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(AppApplication.sContext, "unlock and run Test", Toast.LENGTH_SHORT).show();
                    }
                });
            }
        }
    }
    ...
}

댓글 1개: