[Android Software/Hacking General [Developers Only]] hansip87:
Hi again lads/gents,

Today i want to share to you how to enable 2G/3G Toggle feature that was commonly found on CyanogenMod and/or AOKP into your custom Android ROM. My code is based from Xperia Ray but shouldn't be too different between different phone version, because the concept in essence is to bring up the default toggle hidden in Settings menu to be switchable with external app or button.

The common problem when we try to implement this kind of mod is that Android system prevents system changes from outside system's apk, and added to the fact that the only apk allowed for Network Mode changes is the one with uid set as "phone" at its AndroidManifest.xml. When you thought it's a part of Settings.apk, actually Settings.apk only calls for Phone.apk Settings page.

So to do a workaround for that, you need to alter the phone.apk itself to be able to receive custom Broadcast from external apk and then switch the mode when required.

This is what you need:

1. Rooted phone

2. Phone.apk (or in my case SemcPhone.apk)

3. Basic Knowledge of Android Broadcast and Receiver concept

4. Custom toggle app/Status Bar Widget (in my case, i'll use lidroid's Power Button on Status Bar as a toggle utility)

5. Any APK Manager, preferably one that can be set to not decode resource file.

The steps are:

1. Place the Receiver files inside Phone.apk

2. Register the Receiver at runtime through Phone Application smali file

3. Compile Phone.apk

4. Implement Toggle mechanism for 2G/3G on Toggling app

1. Place the Receiver files inside Phone.apk

This is used as a Receiver of Broadcast Message from User App to start Toggling the Network Mode. There are 3 files you need to put on decompiled Phone.apk:

smali\com\android\phone\NetworkModeReceiver.smali -> The Main Code

.class public Lcom/android/phone/NetworkModeReceiver;
.super Landroid/content/BroadcastReceiver;
.source "NetworkModeReceiver.java"

# annotations
.annotation system Ldalvik/annotation/MemberClasses;
value = {
Lcom/android/phone/NetworkModeReceiver$1;,
Lcom/android/phone/NetworkModeReceiver$MyHandler;
}
.end annotation

# static fields
.field private static final TAG:Ljava/lang/String; = "NetworkModeReceiver"

# instance fields
.field private mHandler:Lcom/android/phone/NetworkModeReceiver$MyHandler;

# direct methods
.method public constructor <init>()V
.locals 2

.prologue
.line 31
invoke-direct {p0}, Landroid/content/BroadcastReceiver;-><init>()V

new-instance v0, Lcom/android/phone/NetworkModeReceiver$MyHandler;

const/4 v1, 0x0

invoke-direct {v0, p0, v1}, Lcom/android/phone/NetworkModeReceiver$MyHandler;-><init>(Lcom/android/phone/NetworkModeReceiver;Lcom/android/phone/NetworkModeReceiver$1;)V

iput-object v0, p0, Lcom/android/phone/NetworkModeReceiver;->mHandler:Lcom/android/phone/NetworkModeReceiver$MyHandler;

return-void
.end method

# virtual methods
.method public onReceive(Landroid/content/Context;Landroid/content/Intent;)V
.locals 5
.parameter "context"
.parameter "intent"

const-string v0, "com.android.phone.CHANGE_NETWORK_MODE"

.line 41
invoke-virtual {p2}, Landroid/content/Intent;->getAction()Ljava/lang/String;

move-result-object v1

invoke-virtual {v1, v0}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

move-result v1

if-eqz v1, :cond_0

.line 43
const-string v0, "com.android.phone.NEW_NETWORK_MODE"

const v1, 0x0

invoke-virtual {p2, v0, v1}, Landroid/content/Intent;->getIntExtra(Ljava/lang/String;I)I

move-result v1

.line 44
new-instance v0, Ljava/lang/StringBuilder;

invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V

const-string v3, "Receive Network Mode "

invoke-virtual {v0, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

move-result-object v0

invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;

move-result-object v0

invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

move-result-object v0

sget-object v2, Lcom/android/phone/NetworkModeReceiver;->TAG:Ljava/lang/String;

.line 45
invoke-static {v2, v0}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

.line 46
invoke-virtual {p1}, Landroid/content/Context;->getContentResolver()Landroid/content/ContentResolver;

move-result-object v3

const-string v2, "preferred_network_mode"

invoke-static {v3, v2, v1}, Landroid/provider/Settings$Secure;->putInt(Landroid/content/ContentResolver;Ljava/lang/String;I)Z

iget-object v4, p0, Lcom/android/phone/NetworkModeReceiver;->mHandler:Lcom/android/phone/NetworkModeReceiver$MyHandler;

.line 48
const/4 v3, 0x1

invoke-virtual {v4, v3}, Lcom/android/phone/NetworkModeReceiver$MyHandler;->obtainMessage(I)Landroid/os/Message;

move-result-object v4

.line 49
invoke-static {}, Lcom/android/internal/telephony/PhoneFactory;->getDefaultPhone()Lcom/android/internal/telephony/Phone;

move-result-object v3

invoke-interface {v3, v1, v4}, Lcom/android/internal/telephony/Phone;->setPreferredNetworkType(ILandroid/os/Message;)V

:cond_0
return-void
.end method

As you can see, We will use Intent named as "com.android.phone.CHANGE_NETWORK_MODE" with the Extras(or attribute as a layman's term) named as "com.android.phone.NEW_NETWORK_MODE"

smali\com\android\phone\NetworkModeReceiver$1.smali -> Helper Class for MyHandler inner class.

.class synthetic Lcom/android/phone/NetworkModeReceiver$1;
.super Ljava/lang/Object;
.source "NetworkModeReceiver.java"

# annotations
.annotation system Ldalvik/annotation/EnclosingClass;
value = Lcom/android/phone/NetworkModeReceiver;
.end annotation

.annotation system Ldalvik/annotation/InnerClass;
accessFlags = 0x1008
name = null
.end annotation

smali\com\android\phone\NetworkModeReceiver$MyHandler.smali -> Does nothing, only used for Receiving a rollback message from system for UI element - which in this method is not quite usable. But it's there for compatibility reason.

.class Lcom/android/phone/NetworkModeReceiver$MyHandler;
.super Landroid/os/Handler;
.source "NetworkModeReceiver.java"

# annotations
.annotation system Ldalvik/annotation/EnclosingClass;
value = Lcom/android/phone/NetworkModeReceiver;
.end annotation

.annotation system Ldalvik/annotation/InnerClass;
accessFlags = 0x2
name = "MyHandler"
.end annotation

# instance fields
.field final synthetic this$0:Lcom/android/phone/NetworkModeReceiver;

# direct methods
.method private constructor <init>(Lcom/android/phone/NetworkModeReceiver;)V
.locals 0
.parameter

.prologue
.line 628
iput-object p1, p0, Lcom/android/phone/NetworkModeReceiver$MyHandler;->this$0:Lcom/android/phone/NetworkModeReceiver;

invoke-direct {p0}, Landroid/os/Handler;-><init>()V

return-void
.end method

.method synthetic constructor <init>(Lcom/android/phone/NetworkModeReceiver;Lcom/android/phone/NetworkModeReceiver$1;)V
.locals 0
.parameter "x0"
.parameter "x1"

.prologue
.line 628
invoke-direct {p0, p1}, Lcom/android/phone/NetworkModeReceiver$MyHandler;-><init>(Lcom/android/phone/NetworkModeReceiver;)V

return-void
.end method

.method private handleGetPreferredNetworkTypeResponse(Landroid/os/Message;)V
.locals 9
.parameter "msg"

return-void
.end method

.method private handleSetPreferredNetworkTypeResponse(Landroid/os/Message;)V
.locals 2
.parameter "msg"

return-void
.end method

.method private resetNetworkModeToDefault()V
.locals 3

.prologue
return-void
.end method

# virtual methods
.method public handleMessage(Landroid/os/Message;)V
.locals 1
.parameter "msg"

.prologue
.line 635
iget v0, p1, Landroid/os/Message;->what:I

packed-switch v0, :pswitch_data_0

.line 643
:goto_0
return-void

.line 637
:pswitch_0
invoke-direct {p0, p1}, Lcom/android/phone/NetworkModeReceiver$MyHandler;->handleGetPreferredNetworkTypeResponse(Landroid/os/Message;)V

goto :goto_0

.line 640
:pswitch_1
invoke-direct {p0, p1}, Lcom/android/phone/NetworkModeReceiver$MyHandler;->handleSetPreferredNetworkTypeResponse(Landroid/os/Message;)V

goto :goto_0

.line 635
:pswitch_data_0
.packed-switch 0x0
:pswitch_0
:pswitch_1
.end packed-switch
.end method

You need to download from the attachment and put them into smali\com\android\phone folder.

2. Register the Receiver at runtime through Phone Application smali file

Now you have the Receiver installed, you need to initiate and register the Receiver at phone app startup. There are 2 methods available:

Add another line to AndroidManifest.xml -> The simplest one but not practical for system app of stock ROM since the AndroidManifest.xml requires to be signed to be functional.

Alter PhoneApp.smali (it can be named something else->The point is you need Phone's Application main code to be altered) to register the receiver when it's called -> This is the way to go.

This is how you alter the PhoneApp.smali:

Give a space to store the Receiver inside the PhoneApp.smali, named as mModeReceiver (Insert the Bold Line)

.field mLastPhoneState:Lcom/android/internal/telephony/Phone$State;

.field private final mMediaButtonReceiver:Landroid/content/BroadcastReceiver;

[B].field private final mModeReceiver:Landroid/content/BroadcastReceiver;[/B]

.field private mNationalRoamingSettings:I

.field private mNationalRoamingSupported:Z

Initiate the Receiver Object and store it into the available space (Insert the Bold Line)

.method public constructor <init>()V
.
.
.line 233
new-instance v0, Lcom/android/phone/PhoneApp$PhoneAppBroadcastReceiver;

const/4 v1, 0x0

invoke-direct {v0, p0, v1}, Lcom/android/phone/PhoneApp$PhoneAppBroadcastReceiver;-><init>(Lcom/android/phone/PhoneApp;Lcom/android/phone/PhoneApp$1;)V

iput-object v0, p0, Lcom/android/phone/PhoneApp;->mReceiver:Landroid/content/BroadcastReceiver;

[B] .line 236
new-instance v0, Lcom/android/phone/NetworkModeReceiver;

invoke-direct {v0}, Lcom/android/phone/NetworkModeReceiver;-><init>()V

iput-object v0, p0, Lcom/android/phone/PhoneApp;->mModeReceiver:Landroid/content/BroadcastReceiver;[/B]

.line 238
new-instance v0, Lcom/android/phone/SemcMediaButtonBroadcastReceiver;

invoke-direct {v0}, Lcom/android/phone/SemcMediaButtonBroadcastReceiver;-><init>()V

iput-object v0, p0, Lcom/android/phone/PhoneApp;->mMediaButtonReceiver:Landroid/content/BroadcastReceiver;

.
.
.end method

Register the Receiver

The following is to register the Receiver when the Phone app is created (onCreate). When registered, It will start listening to the phone activity so later it can receive a toggle message from toggle app. And not just any message that this receiver can read, We limit this Receiver to only hear a filtered Intent with name of "com.android.phone.CHANGE_NETWORK_MODE" as explained above.

.method public onCreate()V
.
.
.line 630
const-string v1, "com.android.phone.intent.ACTION_DATA_TRAFFIC_SWITCH"

invoke-virtual {v0, v1}, Landroid/content/IntentFilter;->addAction(Ljava/lang/String;)V

.line 631
iget-object v1, p0, Lcom/android/phone/PhoneApp;->mReceiver:Landroid/content/BroadcastReceiver;

invoke-virtual {p0, v1, v0}, Lcom/android/phone/PhoneApp;->registerReceiver(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;

[B] .line 633
new-instance v0, Landroid/content/IntentFilter;

const-string v1, "com.android.phone.CHANGE_NETWORK_MODE"

invoke-direct {v0, v1}, Landroid/content/IntentFilter;-><init>(Ljava/lang/String;)V

iget-object v1, p0, Lcom/android/phone/PhoneApp;->mModeReceiver:Landroid/content/BroadcastReceiver;

invoke-virtual {p0, v1, v0}, Lcom/android/phone/PhoneApp;->registerReceiver(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;[/B]

.line 638
new-instance v0, Landroid/content/IntentFilter;

const-string v1, "android.intent.action.MEDIA_BUTTON"

invoke-direct {v0, v1}, Landroid/content/IntentFilter;-><init>(Ljava/lang/String;)V

.line 642
const/16 v1, 0x3e0

invoke-virtual {v0, v1}, Landroid/content/IntentFilter;->setPriority(I)V

.line 645
iget-object v1, p0, Lcom/android/phone/PhoneApp;->mMediaButtonReceiver:Landroid/content/BroadcastReceiver;

invoke-virtual {p0, v1, v0}, Lcom/android/phone/PhoneApp;->registerReceiver(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;

.
.
.end method

3. Compile the Phone.apk

You need to compile the phone app back. As it was with Sony's code, it was clear that i need to decompile ONLY the source, but not the resource part of the apk because when compiled back it will be broken. So try to decompile it at the source only.

4. Implement the Toggle App to send a Broadcast Intent

Now this is the part where you have to determine what mode is supported by your phone. This is the value of Android supported value that will be sent as a Broadcast Intent:

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*/

So if your phone only supports WCDMA(3G) as max, you should use value 0,1, and 2 only.

The following is my 2G/3G simple toggle mode from Lidroid's Status Bar Widget code. You should adapt the code yourself if you're using something else. But as a guide, this is the logic:

create a new Intent with name "com.android.phone.CHANGE_NETWORK_MODE"

take current mode

Apply the next value into the newly created Intent as Extra named "com.android.phone.NEW_NETWORK_MODE"

Broadcast the Intent to the System (which will be read by Phone.apk).

The logic above can be observed in the method toggleState() on the following code:

.class public Lcom/lidroid/systemui/quickpanel/NetworkModeButton;
.super Lcom/lidroid/systemui/quickpanel/PowerButton;
.source "NetworkModeButton.java"

# static fields
.field private static final CM_MODE_3GSM_ONLY:I = 0x1

.field private static final CM_MODE_3G_ONLY:I = 0x0

.field private static final CM_MODE_3G_PREFERRED:I = 0x2

.field private static final OBSERVED_URIS:Ljava/util/List; = null
.annotation system Ldalvik/annotation/Signature;
value = {
"Ljava/util/List",
"<",
"Landroid/net/Uri;",
">;"
}
.end annotation
.end field

.field private static final TAG:Ljava/lang/String; = "NetworkModeButton"

# direct methods
.method static constructor <clinit>()V
.locals 3

.prologue
const/16 v2, 0x0

.line 21
new-instance v0, Ljava/util/ArrayList;

invoke-direct {v0}, Ljava/util/ArrayList;-><init>()V

sput-object v0, Lcom/lidroid/systemui/quickpanel/NetworkModeButton;->OBSERVED_URIS:Ljava/util/List;

.line 23
sget-object v0, Lcom/lidroid/systemui/quickpanel/NetworkModeButton;->OBSERVED_URIS:Ljava/util/List;

const-string v1, "preferred_network_mode"

invoke-static {v1}, Landroid/provider/Settings$Secure;->getUriFor(Ljava/lang/String;)Landroid/net/Uri;

move-result-object v1

invoke-interface {v0, v1}, Ljava/util/List;->add(Ljava/lang/Object;)Z

return-void
.end method

.method public constructor <init>()V
.locals 2

.prologue
.line 43
invoke-direct {p0}, Lcom/lidroid/systemui/quickpanel/PowerButton;-><init>()V

const-string v0, "toggleNetworkMode"

iput-object v0, p0, Lcom/lidroid/systemui/quickpanel/NetworkModeButton;->mType:Ljava/lang/String;

return-void
.end method

.method private static getCurrentNetworkMode(Landroid/content/Context;)I
.locals 3
.parameter "context"

.prologue
.line 167
const/16 v0, 0x0

.line 169
.local v0, state:I
:try_start_0
invoke-virtual {p0}, Landroid/content/Context;->getContentResolver()Landroid/content/ContentResolver;

move-result-object v1

const-string v2, "preferred_network_mode"

invoke-static {v1, v2}, Landroid/provider/Settings$Secure;->getInt(Landroid/content/ContentResolver;Ljava/lang/String;)I
:try_end_0
.catch Landroid/provider/Settings$SettingNotFoundException; {:try_start_0 .. :try_end_0} :catch_0

move-result v0

.line 173
:goto_0
return v0

.line 171
:catch_0
move-exception v1

goto :goto_0
.end method

# virtual methods
.method protected getObservedUris()Ljava/util/List;
.locals 1
.annotation system Ldalvik/annotation/Signature;
value = {
"()",
"Ljava/util/List",
"<",
"Landroid/net/Uri;",
">;"
}
.end annotation

.prologue
.line 163
sget-object v0, Lcom/lidroid/systemui/quickpanel/NetworkModeButton;->OBSERVED_URIS:Ljava/util/List;

return-object v0
.end method

.method protected getText()I
.locals 1

.prologue
.line 93
const v0, 0x7f08007d

return v0
.end method

.method protected handleLongClick()Z
.locals 3

.prologue
.line 126
new-instance v0, Landroid/content/Intent;

const-string v1, "android.intent.action.MAIN"

invoke-direct {v0, v1}, Landroid/content/Intent;-><init>(Ljava/lang/String;)V

.line 127
.local v0, intent:Landroid/content/Intent;
const-string v1, "com.android.phone"

const-string v2, "com.android.phone.Settings"

invoke-virtual {v0, v1, v2}, Landroid/content/Intent;->setClassName(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;

.line 128
const/high16 v1, 0x1000

invoke-virtual {v0, v1}, Landroid/content/Intent;->addFlags(I)Landroid/content/Intent;

.line 129
iget-object v1, p0, Lcom/lidroid/systemui/quickpanel/NetworkModeButton;->mView:Landroid/view/View;

invoke-virtual {v1}, Landroid/view/View;->getContext()Landroid/content/Context;

move-result-object v1

invoke-virtual {v1, v0}, Landroid/content/Context;->startActivity(Landroid/content/Intent;)V

.line 130
const/4 v1, 0x1

return v1
.end method

[B].method protected toggleState()V
.locals 5

.prologue
const/4 v4, 0x2

const/4 v1, 0x1

const-string v2, "com.android.phone.CHANGE_NETWORK_MODE"

.line 83
iget-object v3, p0, Lcom/lidroid/systemui/quickpanel/NetworkModeButton;->mView:Landroid/view/View;

invoke-virtual {v3}, Landroid/view/View;->getContext()Landroid/content/Context;

move-result-object v3

.line 84
.local v3, context:Landroid/content/Context;
invoke-static {v3}, Lcom/lidroid/systemui/quickpanel/NetworkModeButton;->getCurrentNetworkMode(Landroid/content/Context;)I

move-result v0

.line 85
add-int v0, v0, v1

if-le v0, v4, :cond_0

const/4 v0, 0x0

.line 86
:cond_0
new-instance v4, Landroid/content/Intent;

invoke-direct {v4, v2}, Landroid/content/Intent;-><init>(Ljava/lang/String;)V

const-string v2, "com.android.phone.NEW_NETWORK_MODE"

invoke-virtual {v4, v2, v0}, Landroid/content/Intent;->putExtra(Ljava/lang/String;I)Landroid/content/Intent;

invoke-virtual {v3, v4}, Landroid/content/Context;->sendBroadcast(Landroid/content/Intent;)V

return-void
.end method[/B]

.method protected updateState()V
.locals 3

.line 90
iget-object v1, p0, Lcom/lidroid/systemui/quickpanel/NetworkModeButton;->mView:Landroid/view/View;

invoke-virtual {v1}, Landroid/view/View;->getContext()Landroid/content/Context;

move-result-object v1

invoke-static {v1}, Lcom/lidroid/systemui/quickpanel/NetworkModeButton;->getCurrentNetworkMode(Landroid/content/Context;)I

move-result v0

.line 92
const v1, 0x0

if-ne v0, v1, :cond_0

const v2, 0x7f0200d4

iput v2, p0, Lcom/lidroid/systemui/quickpanel/NetworkModeButton;->mIcon:I

const/4 v2, 0x1

iput v2, p0, Lcom/lidroid/systemui/quickpanel/NetworkModeButton;->mState:I

.line 93
:goto_0
return-void

.line 94
:cond_0
const v1, 0x1

if-ne v0, v1, :cond_1

const v2, 0x7f0200d3

iput v2, p0, Lcom/lidroid/systemui/quickpanel/NetworkModeButton;->mIcon:I

const/4 v2, 0x2

iput v2, p0, Lcom/lidroid/systemui/quickpanel/NetworkModeButton;->mState:I

goto :goto_0

.line 96
:cond_1
const v2, 0x7f0200d5

iput v2, p0, Lcom/lidroid/systemui/quickpanel/NetworkModeButton;->mIcon:I

const/4 v2, 0x1

iput v2, p0, Lcom/lidroid/systemui/quickpanel/NetworkModeButton;->mState:I

goto :goto_0
.end method

Or you could download from attachment if you happen to use Lidroid's code too.

Compile back your apk and voila! ;)

Hope this tutorial helps :) Any question, please reply and i'll help as best as i can. :)