안드로이드 시스템 설정 수정 권한 - andeuloideu siseutem seoljeong sujeong gwonhan

Guter Mann 2019. 4. 3. 13:44

앱에서 시스템 설정 변경

앱(3rd party app.)에서 안드로이드 설정(Setting)을 읽어오거나 변경이 필요한 경우가 있습니다.

여기서 설정은 아래 그림처럼 설정 앱(com.android.settings, System app.) 또는 퀵세팅 타일의 항목을 말합니다.

이후 "앱"이라고 언급하면 3rd party app.으로 생각하시면 됩니다.

 

안드로이드 시스템 설정 수정 권한 - andeuloideu siseutem seoljeong sujeong gwonhan

하나의 항목에는 이름(Name)과 값(Value)이 짝으로 기록되어집니다.

이는 SharedPreference의 키와 값이 짝으로 기록되는 데이터 저장방식과 유사합니다.

예로 들면 위 그림에서 설정 앱의 밝기 자동 조절 항목의 경우 이름과 (사용할 수 있는) 값은 아래와 같이 되어 있습니다.

/**
* Control whether to enable automatic brightness mode.
*/
public static final String SCREEN_BRIGHTNESS_MODE = "screen_brightness_mode";
/**
* SCREEN_BRIGHTNESS_MODE value for manual mode.
*/
public static final int SCREEN_BRIGHTNESS_MODE_MANUAL = 0;

/**
* SCREEN_BRIGHTNESS_MODE value for automatic mode.
*/
public static final int SCREEN_BRIGHTNESS_MODE_AUTOMATIC = 1;

그리고, 이 이름과 값은 아래 경로에 저장됩니다.

[Android 6.0 이전]

 /data/data/com.android.providers.settings/databases/settings.db

[Android 6.0 이후]

/data/system/users/0/settings_system.xml

/data/system/users/0/settings_secure.xml

/data/system/users/0/settings_global.xml

앱에서 위와 같은 Database나 파일에 접근하기 위해서는 Content provider를 이용해야 합니다.

당연히 설정(Settings)에서도 이를 위한 SettingsProvider라는 Native Content provider가 존재합니다. 

하지만, 앱에서 설정에 접근하기 위해서 주로 사용하는 방법은 SettingsProvider의 설정 항목을 쿼리해서 참조할 수 있도록 만든 Settings 클래스(android.provider.settings)를 이용하는 것입니다.

Settings 클래스는 SettingsProvider에 대한 접근을 단순화해주는 클래스라고 보면 됩니다.

Settings-SettingsProvider 관련한 자세한 설명과 분석은 나중에 기회가 되면 따로 설명해 드리고 여기서는 Settings 클래스에 대해 간략한 설명만 하도록 하겠습니다.

NameValueTable 클래스

- 설정 항목의 데이터(이름과 값)를 데이터베이스나 파일에 기록하는 기본 클래스입니다.

Global, Secure, System 클래스

 - 설정 항목의 유형에 따라서 NameValueTable 클래스를 상속받은 Global, Secure, System 클래스가 사용됩니다.

Global의 경우 MultiUser(Android 4.2 버전에서 도입)와 관련됩니다. MultiUser를 위한 공용 설정 데이터 접근 용도입니다.

Secure의 경우 보안 관련한 설정 데이터 접근 용도입니다.

System의 경우 시스템과 관련한 설정 데이터 접근 용도입니다.

참고로 System 항목이었지만 이후에 Global 또는 Secure로 이동된 항목들도 많이 있습니다.

설정 항목 읽기

value = Settings.Global|Secure|System.getInt|getLong|getFloat|getString(ContentResolver cr, String name)

설정 항목 유형과 데이터 종류에 따라서 여러 조합으로 만들어집니다.

▶ 밝기 조절 모드 구하기

int brightnessMode = Settings.System.getInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE);

▶ 위치 정보 모드 구하기

int locationMode = Settings.Secure.getInt(getContentResolver(), Settings.Secure.LOCATION_MODE);

▶ 개발자 옵션 - 디버깅할 앱 패키지명 구하기

String debugApp = Settings.Global.getString(getContentResolver(), Settings.Global.DEBUG_APP);

참고로 getInt, getLong, getFloat을 사용할 때 설정값이 존재하지 않는 경우에 대한 아래와 같은 에러 핸들링이 필요합니다.

try {
locationMode = Settings.Secure.getInt(getContentResolver(), Settings.Secure.LOCATION_MODE);
} cath (Settings.SettingNotFoundException e) {
// Error handling
}

또는 설정값이 존재하지 않는 경우의 디폴트 값을 세 번째 파라미터에 미리 정해줄 수도 있습니다.

int locationMode = Settings.Secure.getInt(getContentResolver(), Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_NONE);

설정 쓰기 권한

<android.permission.WRITE_SETTINGS>

설정을 읽는 것과 달리 설정에 쓰는 것에는 권한이 필요하고 제한도 따릅니다.

"설정 쓰기" 권한 획득 없이 설정 항목에 기록하려고 하면 아래의 오류를 만나게 됩니다.

우선 작업은 AndroidManifest.xml 파일에 아래와 같이 "설정 쓰기" 권한을 넣어주는 것입니다.

<uses-permission android:name="android.permission.WRITE_SETTINGS"
tools:ignore="ProtectedPermissions"/>

안드로이드 6.0 이전까지는 이렇게만 해주면 앱의 설치단계에서 "설정 쓰기" 권한을 얻게 되어 설정 항목 변경이 가능합니다.

안드로이드 6.0 이후부터는 "설정 쓰기" 권한은 보호 수준이 서명 권한(Signature permission)이라서 플랫폼과 동일한 키로 서명된 앱만 정상 권한처럼 설치단계에서 권한을 얻을 수 있지만, 이렇게 동작할 수 있는 것은 시스템 앱의 경우에만 해당합니다.

이에 앱에서는 Settings 클래스의 ACTION_MANAGE_WRITE_SETTINGS 인텐트를 보내서 사용자의 승인을 요청합니다.

위험 권한(Dangerous permission)을 대상으로 하는 일반적인 런타임 권한 처리와는 차이가 있습니다.

일반적인 런타임 권한 요청에 대해서는 아래 글을 참조하시면 됩니다.

2019/02/07 - 안드로이드 런타임 권한 확인 및 요청 처리하기 (Android Runtime permission check and request)

런타임 권한 확인은 "checkSelfPermission()"이 사용되었는데, 설정 쓰기 권한 확인은 Settings.System.canWrite()를 이용합니다. 

아래 코드를 보면, Settings.System.canWrite()로 권한 확인해서 없는 경우에 토스트 메시지를 띄우고, 메시지가 사라지면서 시스템 설정 수정을 위한 액티비티로 넘어가게 되어 있습니다.

private boolean permissionCheckAndRequest() {
boolean permission;

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
permission = Settings.System.canWrite(getApplicationContext());
} else {
permission = ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.WRITE_SETTINGS)
== PackageManager.PERMISSION_GRANTED;
}

mHasWriteSettingsPermission = permission;

if (!permission) {
Toast.makeText(getApplicationContext(), "시스템 설정(밝기 조절 세팅, 화면 회전)을 변경하기 위해서 시스템 변경할 수" +
" 있는 권한이 필요합니다." +
"\n잠시 후에 시스템 설정 변경 창으로 이동합니다. 권한을 [허용]해주세요.", Toast.LENGTH_LONG).show();

new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, WRITE_SETTINGS_PERMISSION_REQUEST_CODE);
} else {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_SETTINGS},
WRITE_SETTINGS_PERMISSION_REQUEST_CODE);
}
}
}, 3500);

return false;
}
return true;
}

아래 그림이 "설정 쓰기" 권한을 얻기 위해서 ACTION_MANAGE_WRITE_SETTINGS 인텐트를 보내면 전환되는 액티비티입니다.

단말 업체별 텍스트 등의 차이는 있습니다만 권한 활성화를 위한 스위치를 갖춘 UI로 되어 있는 것은 동일합니다.

 

권한 획득을 위해서 스위치 활성화하고 앱으로 복귀하면 onActivityResult()로 들어오는데 다시 한번 Settings.System.canWrite()를 실행해서 권한 여부를 확인합니다. (권한 획득하지 못한 경우는 종료처리)

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);

boolean permission;
if (requestCode == WRITE_SETTINGS_PERMISSION_REQUEST_CODE) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
permission = Settings.System.canWrite(this);
} else {
permission = ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.WRITE_SETTINGS)
== PackageManager.PERMISSION_GRANTED;
}

mHasWriteSettingsPermission = permission;

if (permission) {
initBrightness();
mButtonRotation.setVisibility(View.VISIBLE);
} else {
Toast.makeText(getApplicationContext(), "시스템 설정(밝기 조절 세팅)을 변경을 위한 권한이 없어서 앱을 종료하였습니다",
Toast.LENGTH_LONG).show();
finish();
}
}
}

설정 항목 쓰기

Settings.Global|Secure|System.putInt|putLong|putFloat|putString(ContentResolver cr, String name,

                                                                                           int|long|float|String value)

설정 항목의 유형 중에 Global과 Secure는 시스템 앱에서만 쓰기가 가능합니다.

시스템 앱이 아닌 경우에 Global과 Secure 항목을 변경하려고 하면 아래와 같은 오류가 발생합니다.

이는 AndroidManifest.xml에 android.permission.WRITE_SECURE_SETTINGS를 추가하더라도 발생하는 현상입니다.

즉, 앱에서는 System 유형의 설정 항목만 쓰기가 가능합니다.

▶ 밝기 조절 모드를 수동으로 변경

    Settings.System.putInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE,

                        Settings.System.SCREEN_BRIGHTNESS_MANUAL);

▶ 화면을 자동 회전 설정

    Settings.System.putInt(getContentResolver(), Settings.System.ACCELEROMETER_ROTATION, 1);

▶ 화면을 180도 회전

    Settings.System.putInt(getContentResolver(), Settings.System.USER_ROTATION, 2);

▶ 버튼음/터치 소리 활성화

    Settings.System.putInt(getContentResolver(), Settings.System.SOUND_EFFECTS_ENABLED, 1);

▶ 폰트 배율을 2로 조정

    Settings.System.putFloat(getContentResolver(), Settings.System.FONT_SCALE, 2.0f);

시스템 설정 변경을 이용한 화면 밝기 및 화면 방향 조절 예제

레이아웃 (activity_main.xml)

- 밝기의 설정값을 보여주는 TextView

- 화면 밝기 조절을 위한 SeekBar

- 화면 방향 조절을 위한 Button

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/food_1898194_1280"
tools:context=".MainActivity">

<TextView
android:id="@+id/tvBrightness"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:background="#40000000"
android:text="화면 밝기값"
android:textColor="@android:color/white"
android:textSize="20dp"
android:textStyle="bold"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/tvBrightnessValue"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />

<TextView
android:id="@+id/tvBrightnessValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:background="#40000000"
android:text=""
android:textColor="@android:color/white"
android:textSize="20dp"
android:textStyle="bold"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/BrightnessSeekBar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />

<SeekBar
android:id="@+id/BrightnessSeekBar"
android:layout_width="255dp"
android:layout_height="20dp"
android:max="255"
android:progress="1"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.75" />

<Button
android:id="@+id/btnRotation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="화면 회전"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/BrightnessSeekBar" />
</android.support.constraint.ConstraintLayout>

자바 (MainActivity.java)

※설정 쓰기 권한 처리 소스는 위에서 언급되어 있습니다.

화면 밝기 조절을 위한 SeekBar 리스너 / 화면 방향 조절을 위한 Button 리스너 처리

 - changeScreenBrightness에 SeekBar로 조절한 값 전달

 - 화면 방향 조절 버튼은 누르면 바로 시스템 설정에 수정 반영 / 누를 때마다 방향이 변경되도록 처리

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextViewBrightnessValue = findViewById(R.id.tvBrightnessValue);

mSeekBarBrightness = findViewById(R.id.BrightnessSeekBar);
mSeekBarBrightness.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
mTextViewBrightnessValue.setText(String.valueOf(progress));
changeScreenBrightness(progress);
}

@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}

@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});

mButtonRotation = findViewById(R.id.btnRotation);
mButtonRotation.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Settings.System.putInt(getContentResolver(), Settings.System.USER_ROTATION, ++nRotationValue%4);
}
});
}

화면 밝기 조절

 - 넘어온 SeekBar 값으로 실제 화면 밝기 조절하는 코드

 - 넘어온 SeekBar 값을 화면 밝기 설정값으로 저장하는 코드(changeBrightnessSystemSetting)

private void changeScreenBrightness(int value) {
Window window = getWindow();
WindowManager.LayoutParams layoutParams = window.getAttributes();
layoutParams.screenBrightness = value * 1.0f / 255;
window.setAttributes(layoutParams);
changeBrightnessSystemSetting(value);
}private void changeBrightnessSystemSetting(int value) {
    ContentResolver cResolver = getContentResolver();
    Settings.System.putInt(cResolver, Settings.System.SCREEN_BRIGHTNESS_MODE, Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
    Settings.System.putInt(cResolver, Settings.System.SCREEN_BRIGHTNESS, value);
}

화면 밝기 백업 / 복원

 - 앱 실행 전 화면 밝기 저장

 - 앱 종료 때 저장된 화면 밝기로 복원

@Override
protected void onResume() {
super.onResume();

if (!isFinishing() && permissionCheckAndRequest()) {
initBrightness();
initRotation();
}
}

@Override
protected void onPause() {
super.onPause();
if (mHasWriteSettingsPermission) {
restoreBrightnessSystemSetting();
}
}

private void backupBrightnessSystemSetting() {
mBrightnessModeBackup = Settings.System.getInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
mBrightnessValueBackup = Settings.System.getInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS, 128);
Toast.makeText(getApplicationContext(), "[Screen brightness system setting]" +
"\n\nMode: " + ((mBrightnessModeBackup==Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) ?"Automatic":"Manual")
+ ((mBrightnessModeBackup==Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)?"":"\nValue: " + mBrightnessValueBackup),
Toast.LENGTH_LONG).show();
}

private void restoreBrightnessSystemSetting() {
Settings.System.putInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE, mBrightnessModeBackup);
if (mBrightnessModeBackup == Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL) {
Settings.System.putInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS, mBrightnessValueBackup);
}
}

메니페스트(AndroidManifest.xml)

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="kr.happydev.systemsettingcontrol">
<uses-permission android:name="android.permission.WRITE_SETTINGS"
tools:ignore="ProtectedPermissions"/>

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>

프로젝트 압축파일

안드로이드 시스템 설정 수정 권한 - andeuloideu siseutem seoljeong sujeong gwonhan
SystemSettingControl.zip

이번 글은 여기까지입니다. 읽어주셔서 감사합니다.