snakeman
snakeman

Reputation: 326

How to access setPreferredNetworkType in Android source

I have a question for you, I am trying to select the Preferred Network Type on my Android phone. As you can do by doing following steps:

  1. Dial ##4636##
  2. Choose "Phone Information"
  3. Go bottom
  4. Choose preferred Network Type on menu

So after some searches on the source code I found the right class: Phone.java in (\frameworks\base\telephony\java\com\android\internal\telephony)

So with the nice tips of Vinay: How to disable Mobile Data on Android Who is using java reflexion to acces to hidden classes, I tried too by doing:

Method setPrefNetmethod;
Class telephonyManagerClass;
Object ITelephonyStub;
Class ITelephonyClass;

TelephonyManager telephonyManager = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
telephonyManagerClass = Class.forName(telephonyManager.getClass().getName());
Method getITelephonyMethod = telephonyManagerClass.getDeclaredMethod("getITelephony");
getITelephonyMethod.setAccessible(true);
ITelephonyStub = getITelephonyMethod.invoke(telephonyManager);
ITelephonyClass = Class.forName(ITelephonyStub.getClass().getName());

setPrefNetmethod = ITelephonyClass.getDeclaredMethod("setPreferredNetworkType",new Class[] { Integer.class, Message.class });

Message response = Message.obtain();
setPrefNetmethod.setAccessible(false);

setPrefNetmethod.invoke(ITelephonyStub, new Object[] { network_mode, response });

But the problem is that I have this error on DDMS:

03-25 18:18:45.937: WARN/System.err(2989): java.lang.NoSuchMethodException: setPreferredNetworkType 03-25 18:18:45.937: WARN/System.err(2989): at java.lang.ClassCache.findMethodByName(ClassCache.java:308)

So do you have an idea to access setPreferredNetworkType or choose programmaticaly my preferred network type ?

For information (In RILConstants.java) :

/* NETWORK_MODE_* See ril.h RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE */
int NETWORK_MODE_WCDMA_PREF     = 0; /* GSM/WCDMA (WCDMA preferred) */
int NETWORK_MODE_GSM_ONLY       = 1; /* GSM only */
int NETWORK_MODE_WCDMA_ONLY     = 2; /* WCDMA only */
int NETWORK_MODE_GSM_UMTS       = 3; /* GSM/WCDMA (auto mode, according to PRL)
                                        AVAILABLE Application Settings menu*/
int NETWORK_MODE_CDMA           = 4; /* CDMA and EvDo (auto mode, according to PRL)
                                        AVAILABLE Application Settings menu*/
int NETWORK_MODE_CDMA_NO_EVDO   = 5; /* CDMA only */
int NETWORK_MODE_EVDO_NO_CDMA   = 6; /* EvDo only */
int NETWORK_MODE_GLOBAL         = 7; /* GSM/WCDMA, CDMA, and EvDo (auto mode, according to PRL)
                                        AVAILABLE Application Settings menu*/
int PREFERRED_NETWORK_MODE      = NETWORK_MODE_WCDMA_PREF;

Upvotes: 9

Views: 13279

Answers (5)

Samantha
Samantha

Reputation: 1001

First you have to add below permission in your manifest;

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

Then use below code to set preferred network (please note your app has to be OEM signed [System permission] for below to work);

public boolean setPreferredNetworkType(int networkType, int timeout) {
    Boolean result = false;
    try {
        TelephonyManager telephonyManager = (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
        final Method getPreferredNetworkType = telephonyManager.getClass().getDeclaredMethod("getPreferredNetworkType", int.class);
        final Method setPreferredNetworkType = telephonyManager.getClass().getDeclaredMethod("setPreferredNetworkType", int.class, int.class);
        final Method getSubId = telephonyManager.getClass().getDeclaredMethod("getSubId");
        getPreferredNetworkType.setAccessible(true);
        setPreferredNetworkType.setAccessible(true);
        getSubId.setAccessible(true);
        Integer currentNetworkType = (Integer) getPreferredNetworkType.invoke(telephonyManager, getSubId.invoke(telephonyManager));
        if (currentNetworkType != networkType) {
            setPreferredNetworkType.invoke(telephonyManager, getSubId.invoke(telephonyManager), networkType);
            while (timeout > 0) {
                currentNetworkType = (Integer) getPreferredNetworkType.invoke(telephonyManager, getSubId.invoke(telephonyManager));
                if (currentNetworkType == networkType) {
                    result = true;
                    break;
                }
                try {
                    Thread.sleep(THREAD_WAIT_TIMEOUT_IN_MS);
                } catch (InterruptedException e) {
                }
                timeout -= THREAD_WAIT_TIMEOUT_IN_MS;
            }
        } else {
            result = true;
        }
    } catch (SecurityException se) {
    } catch (Exception e) {
    }

    return result;
}

In above you can use the RILConstants.java Integer values as 'networkType'.

Upvotes: 1

Tolio
Tolio

Reputation: 1033

For this to work, your app must be signed with the system key or have carrier privilege. Otherwise the app will throw java.lang.SecurityException: No modify permission or carrier privilege.

My app runs on Android 5.1 Lollipop(API 22) and is signed with the system key, so this is the only configuration I can confirm for sure that works. I can't confirm the carrier privilege approach.

AndroidManifest.xml

Add this permission to your app manifest. If you are using Android Studio, it will probably mark this line as an error because only system apps can have this permission. If you can sign your app with the system keys, don't worry.

<uses-permission android:name="android.permission.MODIFY_PHONE_STATE"/>

Get Preferred Network

The return is defined in RILConstants.java, e.g. RILConstants.NETWORK_MODE_WCDMA_PREF

public int getPreferredNetwork() {
    Method method = getHiddenMethod("getPreferredNetworkType", TelephonyManager.class, null);
    int preferredNetwork = -1000;
    try {
        preferredNetwork = (int) method.invoke(mTelephonyManager);
        Log.i(TAG, "Preferred Network is ::: " + preferredNetwork);
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }

    return preferredNetwork;
}

Set Preferred Method.

The parameter must be based on RILConstants.java, e.g.: RILConstants.NETWORK_MODE_LTE_ONLY

public void setPreferredNetwork(int networkType) {
    try {
        Method setPreferredNetwork = getHiddenMethod("setPreferredNetworkType",
                TelephonyManager.class, new Class[] {int.class});
        Boolean success = (Boolean)setPreferredNetwork.invoke(mTelephonyManager,
                networkType);
        Log.i(TAG, "Could set Network Type ::: " + (success.booleanValue() ? "YES" : "NO"));
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
}

This is an utility method to access the hidden API methods.

/**
 * Get a hidden method instance from a class
 * @param methodName The name of the method to be taken from the class
 * @param fromClass The name of the class that has the method
 * @return A Method instance that can be invoked
 */
public Method getHiddenMethod(String methodName, Class fromClass, Class[] params) {
    Method method = null;
    try {
        Class clazz = Class.forName(fromClass.getName());
        method = clazz.getMethod(methodName, params);
        method.setAccessible(true);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    }

    return method;
}

Upvotes: 0

You can try this post: http://www.josemauricio.net/?p=486

It's using these AT commands:

KitKat: echo "AT^SYSCONFIG=13,1,1,2\r" > /dev/smd0

Lollipop: echo "AT+WS46=12\r" > /dev/umts_at0

One of the main issues, is founding the right command and serial port your device uses.

Upvotes: 0

CelinHC
CelinHC

Reputation: 1984

Here are some hardcore reflection https://github.com/TheMasterBaron/Toggle-2G

Upvotes: 0

Noogler
Noogler

Reputation: 51

Here's a nice guide how to access the internal API: https://devmaze.wordpress.com/2011/01/18/using-com-android-internal-part-1-introduction/

From own experience I can tell you that the access generally works but you will get a runtime exception because only system apps have permissions for setting Preferred Network Type.

This means your app also has to be installed in the system folder AND has to be signed with system key, which is the crux of the matter....

Upvotes: 5

Related Questions