web-dev-qa-db-ja.com

Android L以降の場合、setMobileDataEnabledメソッドは呼び出しできなくなりました

私は、setMobileDataEnabled()メソッドがリフレクションを介して呼び出せなくなったことに関して、Googleに Issue 78084 を記録しました。 Android 2.1(API 7)からリフレクション経由でAndroid 4.4(API 19)まで呼び出し可能だったが、Android = L以降、ルートであっても、setMobileDataEnabled()メソッドは呼び出しできません。

公式の対応では、問題は「クローズ」であり、ステータスは「WorkingAsIntended」に設定されています。 Googleの簡単な説明は次のとおりです。

プライベートAPIは安定しておらず、予告なく消滅する可能性があるためプライベートです。

はい、Google、リフレクションを使用して、Androidが登場する前であっても)隠されたメソッドを呼び出すリスクを認識していますが、代替手段がある場合は、より確実な回答を提供する必要があります、setMobileDataEnabled()と同じ結果を達成するため(Googleの決定に不満がある場合は、 Issue 78084 にログインし、できるだけ多くスターを付けてGoogleに知らせます。その方法のエラー。)

だから、あなたへの私の質問は次のとおりです:Androidデバイスでモバイルネットワーク機能をプログラムで有効または無効にすることになると、私たちは行き止まりになりますか? Android 5.0(Lollipop)以降で回避策があれば、このスレッドであなたの答え/議論を聞いてみたいです。

以下のコードを使用して、setMobileDataEnabled()メソッドが使用可能かどうかを確認しました。

final Class<?> conmanClass = Class.forName(context.getSystemService(Context.CONNECTIVITY_SERVICE).getClass().getName());
final Field iConnectivityManagerField = conmanClass.getDeclaredField("mService");
iConnectivityManagerField.setAccessible(true);
final Object iConnectivityManager = iConnectivityManagerField.get(context.getSystemService(Context.CONNECTIVITY_SERVICE));
final Class<?> iConnectivityManagerClass = Class.forName(iConnectivityManager.getClass().getName());
final Method[] methods = iConnectivityManagerClass.getDeclaredMethods();
for (final Method method : methods) {
    if (method.toGenericString().contains("set")) {
        Log.i("TESTING", "Method: " + method.getName());
    }
}

しかし、そうではありません。

[〜#〜] update [〜#〜]:現在、デバイスがルート化されている場合、モバイルネットワークを切り替えることができます。ただし、ルート化されていないデバイスについては、モバイルネットワークを切り替える普遍的な方法がないため、調査プロセスです。

47
ChuongPham

ChuongPhamが投稿したサービスコールメソッドは、すべてのデバイスで一貫して機能しないことに気付きました。

私は、ルート化されたすべてのデバイスで問題なく動作する次のソリューションを見つけました。

suを介して以下を実行します

モバイルデータを有効にするには

svc data enable

モバイルデータを無効にするには

svc data disable

これが最も簡単で最良の方法だと思います。

編集:2回のダウン投票は、商業上の理由だと私が信じているものでした。その人はコメントを削除しました。自分で試してみてください、うまくいきます!また、コメントで作業者によって動作することが確認されました。

9
A.J.

さらにいくつかの洞察と可能な解決策を共有するために(根ざしたデバイスとシステムアプリの場合)。

ソリューション#1

setMobileDataEnabledメソッドはConnectivityManagerにもう存在しないようで、この機能はTelephonyManagergetDataEnabledの2つのメソッドでsetDataEnabledに移動しました。以下のコードでわかるように、これらのメソッドをリフレクションで呼び出してみました。

public void setMobileDataState(boolean mobileDataEnabled)
{
    try
    {
        TelephonyManager telephonyService = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);

        Method setMobileDataEnabledMethod = telephonyService.getClass().getDeclaredMethod("setDataEnabled", boolean.class);

        if (null != setMobileDataEnabledMethod)
        {
            setMobileDataEnabledMethod.invoke(telephonyService, mobileDataEnabled);
        }
    }
    catch (Exception ex)
    {
        Log.e(TAG, "Error setting mobile data state", ex);
    }
}

public boolean getMobileDataState()
{
    try
    {
        TelephonyManager telephonyService = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);

        Method getMobileDataEnabledMethod = telephonyService.getClass().getDeclaredMethod("getDataEnabled");

        if (null != getMobileDataEnabledMethod)
        {
            boolean mobileDataEnabled = (Boolean) getMobileDataEnabledMethod.invoke(telephonyService);

            return mobileDataEnabled;
        }
    }
    catch (Exception ex)
    {
        Log.e(TAG, "Error getting mobile data state", ex);
    }

    return false;
}

コードを実行すると、SecurityExceptionNeither user 10089 nor current process has Android.permission.MODIFY_PHONE_STATE.

そのため、これは内部APIに対する意図的な変更であり、以前のバージョンでそのハックを使用していたアプリでは使用できなくなりました。

(開始暴言:その恐ろしいAndroid.permission.MODIFY_PHONE_STATE許可...終了暴言)。

幸いなことに、MODIFY_PHONE_STATE権限を取得できるアプリを構築している場合(システムアプリのみがそれを使用できます)、上記のコードを使用してモバイルデータの状態を切り替えることができます。

ソリューション#2

モバイルデータの現在の状態を確認するには、mobile_data の分野 Settings.Global(公式ドキュメントには記載されていません)。

Settings.Global.getInt(contentResolver, "mobile_data");

また、モバイルデータを有効/無効にするには、ルート化されたデバイスでシェルコマンドを使用できます(コメントでのフィードバックを歓迎するために、基本的なテストを実行しただけです)。次のコマンドをルートとして実行できます(1 =有効、0 =無効):

settings put global mobile_data 1
settings put global mobile_data 0
6
Muzikant

su -c 'service call phone 83 i32 1'ソリューションは、ルート化されたデバイスに最も信頼できることがわかりました。 Phong Leのリファレンスのおかげで、リフレクションを使用してベンダー/ OS固有のトランザクションコードを取得することで改善しました。たぶんそれは他の誰かに役立つでしょう。したがって、ソースコードは次のとおりです。

    public void changeConnection(boolean enable) {
        try{
            StringBuilder command = new StringBuilder();
            command.append("su -c ");
            command.append("service call phone ");
            command.append(getTransactionCode() + " ");
            if (Build.VERSION.SDK_INT >= 22) {
                SubscriptionManager manager = SubscriptionManager.from(context);
                int id = 0;
                if (manager.getActiveSubscriptionInfoCount() > 0)
                    id = manager.getActiveSubscriptionInfoList().get(0).getSubscriptionId();
                command.append("i32 ");
                command.append(String.valueOf(id) + " ");
            }
            command.append("i32 ");
            command.append(enable?"1":"0");
            command.append("\n");
            Runtime.getRuntime().exec(command.toString());
        }catch(IOException e){
            ...
        }
    }

    private String getTransactionCode() {
        try {
            TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
            Class telephonyManagerClass = Class.forName(telephonyManager.getClass().getName());
            Method getITelephonyMethod = telephonyManagerClass.getDeclaredMethod("getITelephony");
            getITelephonyMethod.setAccessible(true);
            Object ITelephonyStub = getITelephonyMethod.invoke(telephonyManager);
            Class ITelephonyClass = Class.forName(ITelephonyStub.getClass().getName());

            Class stub = ITelephonyClass.getDeclaringClass();
            Field field = stub.getDeclaredField("TRANSACTION_setDataEnabled");
            field.setAccessible(true);
            return String.valueOf(field.getInt(null));
        } catch (Exception e) {
            if (Build.VERSION.SDK_INT >= 22)
                return "86";
            else if (Build.VERSION.SDK_INT == 21)
                return "83";
        }
        return "";
    }

更新:

一部のユーザーは、この方法でモバイルネットワークをオンにすることに問題があると報告しています(オフにすると正常に機能します)。誰にも解決策がありますか?

Update2:

Android 5.1コードを少し掘り下げた後、トランザクションの署名が変更されていることがわかりました。 Android 5.1は、マルチSIMの公式サポートを提供します。そのため、トランザクションには、最初のパラメーターとしていわゆるサブスクリプションIDが必要です( 詳細はこちら )。この状況の結果、コマンドsu -c 'service call phone 83 i32 1'はAndroid 5.1でMobile Netをオンにしません。したがって、Android 5.1の完全なコマンドは次のようになりますsu -c 'service call phone 83 i32 0 i32 1'i32 0はsubId、i32 1はコマンド0-オフ、1-オン) 。この修正で上記のコードを更新しました。

3
nickkadrov

コメントするのに十分な評判はありませんが、すべての答えを試したところ、次のことがわかりました。

ChuongPham:8を使用する代わりに、リフレクションを使用してTRANSACTION_setDataEnabledから変数com.Android.internal.telephony.ITelephonyの値を取得し、すべてのAndroid =ブランドに関係なく、5台以上のデバイス。

Muzikant:アプリが/system/priv-app/ディレクトリに移動された場合に動作します(rgruetに感謝します)。そうでなければ、ルート経由でも動作します!モバイルネットワークの変更を行う前に、アプリを再起動する必要があることをユーザーに通知する必要があります。

AJ:仕事です。サブスクリプションサービスをオフにしないため、テストしたデバイスのバッテリーはかなり消耗しました。 AJの解決策は[〜#〜] not [〜#〜]主張にもかかわらずムジカントの解決策と同等です。サムスン、ソニー、およびLGストックROM(私は徹底しています)をデバッグすることでこれを確認できます。ネクサスとモトローラのいくつかのROMを手に入れたので、提案されたソリューションでこれらのROMをテストしていない。

とにかく、それが解決策に対する疑念を解消することを願っています。

ハッピーコーディング! PL、ドイツ

[〜#〜] update [〜#〜]:リフレクションを介してTRANSACTION_setDataEnabledフィールドの値を取得する方法を知りたい場合は、以下を実行できます。

private static String getTransactionCodeFromApi20(Context context) throws Exception {
    try {
        final TelephonyManager mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 
        final Class<?> mTelephonyClass = Class.forName(mTelephonyManager.getClass().getName());
        final Method mTelephonyMethod = mTelephonyClass.getDeclaredMethod("getITelephony");
        mTelephonyMethod.setAccessible(true);
        final Object mTelephonyStub = mTelephonyMethod.invoke(mTelephonyManager);
        final Class<?> mTelephonyStubClass = Class.forName(mTelephonyStub.getClass().getName());
        final Class<?> mClass = mTelephonyStubClass.getDeclaringClass();
        final Field field = mClass.getDeclaredField("TRANSACTION_setDataEnabled");
        field.setAccessible(true);
        return String.valueOf(field.getInt(null));
    } catch (Exception e) {
        // The "TRANSACTION_setDataEnabled" field is not available,
        // or named differently in the current API level, so we throw
        // an exception and inform users that the method is not available.
        throw e;
    }
}
3
user4750643

.apkを/system/priv-app/フォルダーに移動してアプリを「システム」にすると、Muzikantのソリューション#1が機能するようですnotto /system/app/ one(@jaumard:たぶんそれがテストが機能しなかった理由です)。

.apkが/system/priv-app/フォルダーにある場合、マニフェストで恐ろしいAndroid.permission.MODIFY_PHONE_STATE権限を正常に要求し、TelephonyManager.setDataEnabledおよびTelephonyManager.getDataEnabledを呼び出すことができます。

少なくともNexus 5で動作します。Android5.0。apkパーマは0144です。reboot変更を考慮に入れるためのデバイス。おそらくこれは回避できます- このスレッド を参照してください。

2
rgruet

Muzikant Solution#2を修正するには

settings put global mobile_data 1

モバイルデータのトグルのみを有効にしますが、接続には何もしません。トグルのみが有効になります。を使用してデータを機能させるには

su -c am broadcast -a Android.intent.action.ANY_DATA_STATE --ez state 1

の追加としてエラーを与える

Android.intent.action.ANY_DATA_STATE

--ezパラメータがブール値に使用されている間、Stringオブジェクトが必要です。参照:PhoneGlobals.JavaおよびPhoneConstants.Java。接続を使用した後、またはコマンドを使用して追加として接続した後

su -c am broadcast -a Android.intent.action.ANY_DATA_STATE --es state connecting

データを有効にするためにまだ何もしません。

1
Sahil Lombar

@ChuongPhamと@ A.Jから最終的なコードを導き出しました。携帯データを有効または無効にします。有効にするにはsetMobileDataEnabled(true);を呼び出し、無効にするにはsetMobileDataEnabled(false);を呼び出します

public void setMobileDataEnabled(boolean enableOrDisable) throws Exception {
    String command = null;
    if (enableOrDisable) {
        command = "svc data enable";
    } else {
        command = "svc data disable";
    }


    executeCommandViaSu(mContext, "-c", command);
}

private static void executeCommandViaSu(Context context, String option, String command) {
    boolean success = false;
    String su = "su";
    for (int i = 0; i < 3; i++) {
        // Default "su" command executed successfully, then quit.
        if (success) {
            break;
        }
        // Else, execute other "su" commands.
        if (i == 1) {
            su = "/system/xbin/su";
        } else if (i == 2) {
            su = "/system/bin/su";
        }
        try {
            // Execute command as "su".
            Runtime.getRuntime().exec(new String[]{su, option, command});
        } catch (IOException e) {
            success = false;
            // Oops! Cannot execute `su` for some reason.
            // Log error here.
        } finally {
            success = true;
        }
    }
}
0