LoginSignup
3
1

More than 3 years have passed since last update.

Oreo以降でもAndroidのWi-FiアクセスポイントをアプリでON/OFF切替できた!

Last updated at Posted at 2020-12-08

qnote Advent Calendar 2020 の9日目です。

今は昔、Android Lの頃はsetWifiApEnabledをリフレクション で呼び出すことでテザリングの開始・停止を切り替えることができていました。

sample.kt
private fun changeTetheringState(state: Boolean) {
    val wifi = applicationContext.getSystemService(WIFI_SERVICE) as WifiManager
    val method = wifi.javaClass.getMethod("setWifiApEnabled", WifiConfiguration::class.java, Boolean::class.javaPrimitiveType)
    method.invoke(wifi, null, state)
}

こんなやつですね

今更ですが、この方法がAndroid 8(APIレベル:26)からは使えなくなっています。
僕も最近になってやろうとしたら使えなくて困りました...。
元々がリフレクションを使ったやり方なので仕方ないですが、なんとかしたいですよね?

なんとかいたしましょう!!!

呼び出すべきメソッド

startTethering.java
@SystemApi
@Deprecated
@RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
public void startTethering(int type, boolean showProvisioningUi, final OnStartTetheringCallback callback, Handler handler)
stopTethering.java
@SystemApi
@Deprecated
@RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
public void stopTethering(int type)

この2つのメソッドをリフレクションで呼び出します。
問題はstartTetheringの第三引数にあるOnStartTetheringCallbackで、これは抽象クラスとなっています。

OnStartTetheringCallback.java
@SystemApi
@Deprecated
public static abstract class OnStartTetheringCallback {
    /**
     * Called when tethering has been successfully started.
     */
    public void onTetheringStarted() {}

    /**
     * Called when starting tethering failed.
     */
    public void onTetheringFailed() {}
}

抽象クラスはリフレクションを使っても生成ができないので詰まってしまいました。

Dexmaker

隠された抽象クラスの生成について調べてみると以下の記事を見つけました。
java - Instance of abstract class with hidden constructor - Stack Overflow

こちらでDexmakerというライブラリを使って抽象クラスを生成できるという記述があったので試していきます。

ライブラリのページにはGradleのdependenciesに以下の記述をするようにあります。
androidTestImplementation ‘com.linkedin.dexmaker:dexmaker-mockito:2.28.0’
今回はテストで使う訳ではないのでimplementationに書き換えてみたのですが、Stack OverflowにあるProxyBuilderなるクラスが存在しません…!

内容が古いのでダメなのか…と、半ば諦めていたのですが、
Stack Overflowでは「You will need dexmaker.1.4.jar and dexmaker-dx.1.4.jar」と書いてあったのでひとまずimplementation ‘com.linkedin.dexmaker:dexmaker:2.28.0’に書き換えてみたところProxyBuilderが見つかりました!

わざわざ古い方でやる事もないと思うのでこのまま進めます。

実装

という訳で実装は以下のようになりました。
エラーハンドリング等々は特に考慮しませんでしたがとりあえず動いたコードになります。

startTethering.kt
private fun startTethering() {
    fun getOnStartTetheringCallbackClass(): Class<*>? {
        return Class.forName("android.net.ConnectivityManager\$OnStartTetheringCallback")
    }

     val connectivityManager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager

     val proxy = ProxyBuilder.forClass(getOnStartTetheringCallbackClass())
            .dexCache(cacheDir).handler(InvocationHandler { proxy, method, args -> ProxyBuilder.callSuper(proxy, method, *args) }).build()

    val method: Method = connectivityManager.javaClass.getDeclaredMethod("startTethering", Int::class.javaPrimitiveType, Boolean::class.javaPrimitiveType, getOnStartTetheringCallbackClass(), Handler::class.java)
    method.invoke(connectivityManager, ConnectivityManager.TYPE_MOBILE, false, proxy, null)
}

今回はテザリングできさえすればよかったのでコールバックは何もさせていませんが、もし「OnStartTetheringCallback」で何かしたいことがある場合には、「InvocationHandler」で「ProxyBuilder.callSuper」している箇所でmethod名で分岐させて処理を書けばOKです。
適当なコールバッククラスを作って事前に引数として渡すのも良さそうです。

「stopTethering」はもはやおまけですね…

stopTethering.kt
private fun stopTethering() {
    val connectivityManager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
    val method: Method = connectivityManager.javaClass.getDeclaredMethod("stopTethering", Int::class.javaPrimitiveType)
    method.invoke(connectivityManager, ConnectivityManager.TYPE_MOBILE)
}

備考

テザリングのON/OFF切替にはシステム設定の変更の許可が必要で、普通のパーミッション許可と違うので、ストアリリースするアプリに組み込むのはちょっと抵抗があるかもしれません…
それから自分の端末ではないので詳細不明ですが一部端末で動作しないようです。

それから、今回使ったメソッドも@Deprecatedなのできっとまた使えなくなる日がくるのでしょう...

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1