@Systemapi, @hide 의 이해

3 분 소요

안드로이드의 버전이 높아질수록 Google에 OEM이나 3rd party app에 대해 제한하는 것들의 범위가 점점 넓어지고 있다.
preload app인데도 불구하고 framework의 api를 사용하지 못하는 것 뿐만 아니라, 나아가서는 framework를 updatable하게 만들기 위해 mainline을 적용하며 OEM에서 아예 소스코드를 수정하지 못하게 만드는 등 제한의 폭은 다양해지고 있다.

내가 담당하는 모듈이 이번 Android 11부터 mainlne의 후보가 되어 이번 포팅을 진행하며 쓰지 못했던 api가 종종 있었다.
(Mainline과 apex 에 대한 설명은 링크를 참조)

비SKD 인터페이스 제한사항

Android 9부터 적용된 사항으로 앱에서 사용할 수 있는 비SDK 인터페이스가 제한되었다. Developer site 참고
간단히 말하면 여기 에 공식적으로 공개되지 않은 SDK는 쓰지 못한다는 것이다. 따라서 reflection의 사용도 금지된다.

WifiManager.java 의 소스코드를 일부 가져와봤다.
isConnectedMacRandomizationSupported()이라는, Connected 상태에서의 Random MAC을 지원하는지 여부를 확인하는 API이다.
(첨언하자면 Random MAC과 연결된 AP에 대한 Random MAC은 다른 개념이다. 최신 단말들은 대부분 Random MAC을 지원하고 있다.)

/**
 * @return true if this device supports connected MAC randomization.
 * @hide
 */
@SystemApi
public boolean isConnectedMacRandomizationSupported() {
  return isFeatureSupported(WIFI_FEATURE_CONNECTED_RAND_MAC);
  }

메소드 위에는 annotaion들이 붙어있다.
그리고 실제로 호출해보았다.

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var wifiManger : WifiManager = getSystemService(Context.WIFI_SERVICE) as WifiManager
        wifiManger.isConnectedMacRandomizationSupported()
    }
Unresolved reference: isConnectedMacRandomizationSupported

isConnectedMacRandomizationSupported 없는 함수라고 표시된다. 있는데?
그럼 @hide, @SystemApi 각각은 무엇을 의미하는걸까?

@hide

When applied to a package, class, method or field, @hide removes that node and all of its children from the documentation.

@hide는 javadoc의 일부이며 pulbic API가 아니기 때문에 사용하면 안됨을 의미한다. @hide를 사용하면 SDK 빌드에 포함되지 않기 때문에 document에는 아예 없는 API가 되는 것이다.
하지만 javadoc의 일부이기 때문에 Platform 내에서 사용 시에는 문제가 없으나 이 역시 Mainline의 영역에 있는 API는 접근 불가능하다.

@SystemApi

실제 존재하는 API임은 맞지만, 공개되지는 않은 API이다. SDK 빌드 시 포함되지만 사용할 수 없고, 사용하고자 한다면 Reflection을 이용해야한다.
빌드 시 framework와 함께 빌드되는 앱(예를 들면 셋팅)은 빌드 타임에 해당 api를 참조하기 때문에 빌드도 되고, 사용도 가능하다.
따라서 @hide API를 쓰고싶으면 @SystemApi를 추가로 붙여주면 사용은 가능하지만 이 역시 Mainline 영역일 경우 빌드 자체가 되지 않고, 이 부분은 OEM만 가능한 부분이다.
실제로 isConnectedMacRandomizationSupported를 reflection으로 호출해보았다. (편의상 여기는 그냥 자바로..)

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "RandomMacTest";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        WifiManager wifiManager = (WifiManager) this.getSystemService(Context.WIFI_SERVICE);

        try {
            Method method = wifiManager.getClass().getMethod("isConnectedMacRandomizationSupported");
            boolean result = (Boolean) method.invoke(wifiManager);
            Log.d(TAG, "Is supporting random MAC? " + result);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}
 01-22 17:17:55.171 D 10305 19954 19954 RandomMacTest: Is supporting random MAC? false

실제로 함수가 정상적으로 invoke 되었고 해당 값도 잘 리턴 되었다.

@RequiresPermission

isConnectedMacRandomizationSupported의 경우 요구되는 permission이 “android.permission.ACCESS_WIFI_STATE” 뿐이기 때문에 3rd party app에서도 우회해서 사용할 수 있지만, 실제로는 다른 이유로 대부분의 함수들을 사용하지 못한다.
예를 들어 저장된 모든 Wi-Fi configuration을 삭제하는 factoryReset()을 Reflection으로 호출해본다고 가정했을 때,

 01-22 17:25:15.928 (+0.001s) W 10305 20682 20682 System.err: java.lang.reflect.InvocationTargetException
 01-22 17:25:15.928 W 10305 20682 20682 System.err: at java.lang.reflect.Method.invoke(Native Method)
 01-22 17:25:15.928 W 10305 20682 20682 System.err: at com.example.myapplication.MainActivity.onCreate(MainActivity.java:34)
...
 01-22 17:25:15.931 W 10305 20682 20682 System.err: Caused by: java.lang.SecurityException: WifiService: Neither user 10305 nor current process has android.permission.NETWORK_SETTINGS.
...
 01-22 17:25:15.932 W 10305 20682 20682 System.err: at android.net.wifi.IWifiManager$Stub$Proxy.factoryReset(IWifiManager.java:3651)
 01-22 17:25:15.932 W 10305 20682 20682 System.err: at android.net.wifi.WifiManager.factoryReset(WifiManager.java:5525)
 01-22 17:25:15.932 W 10305 20682 20682 System.err: ... 17 more

“android.permission.NETWORK_SETTINGS”이 필요하다는 메시지와 함께 InvocationTargetException으로 정상적인 호출이 되지 않는다.

    /**
     * Removes all saved Wi-Fi networks, Passpoint configurations, ephemeral networks, Network
     * Requests, and Network Suggestions.
     *
     * @hide
     */
    @SystemApi
    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
    public void factoryReset() {
        try {
            mService.factoryReset(mContext.getOpPackageName());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

@RequiresPermission으로 해당 퍼미션이 필요함을 명시하고 있다.
그러면 저 퍼미션을 추가하면 될까?
답은 안된다.

Permission is only granted to system apps 

system app만 이 퍼미션을 획득할 수 있다.
위에 언급했던 AOSP 소스를 확인해보면 대부분의 API에 RequiresPermission annotation이 달려있고, 대부분의 퍼미션들은 system apps로 제한되어있다.
참고로 system app에서 factoryReset을 reflection으로 호출해보면 아주 잘 동작한다.


오류 지적은 언제나 환영합니다:)

댓글남기기