For my instrumentation tests I am using Robotium. Mostly I am able to test everything but offline cases.
As soon as I disable data (using adb, F8 shortcut in emulator, etc. ...) the test disconnects. It goes on in the device/emulator but no results are reported.
So, I have got an idea to put just the app in offline mode and not the whole device. The problem is I don't know how...
Using iptablesApi I would need to root my device. I have read that Mobiwol app uses some kind of a VPN to restrict apps internet access without the need of rooting a device.
Question
How does Mobiwol app blocks the internet connection per application? Or is there another way how to test apks offline?
EDIT 12/30/2014
I forgot to say that I am able to run tests offline but I have to start tests when the device is in offline state. Currently, I divided my tests into OFFLINE and ONLINE ones. After running ONLINEs I execute the famous adb kill-server and adb start-server. After that I execute OFFLINEs.
Just making a few suggestions since there seem to be different questions here.
1) If all you want to do is turn off the data before running the OFFLINE test case you might want to simply try using robotium itself to do so..
Example:
For WiFi:
WifiManager wifi=(WifiManager)solo.getCurrentActivity().getSystemService(Context.WIFI_SERVICE);
wifi.setWifiEnabled(false);
For Mobile Data(using reflections):
ConnectivityManager dataManager=(ConnectivityManager)solo.getCurrentActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
Method dataClass = ConnectivityManager.class.getDeclaredMethod(“setMobileDataEnabled”, boolean.class);
dataClass.setAccessible(true);
dataClass.invoke(dataManager, true);
You can do the two above calls in the setup() method before running the individual test case in the OFFLINE suite.
Once all the test case in the OFFLINE suite are done with you can enable the WiFi/DATA back on in the teardown() method at the very end.
2) Looking at the app that you posted in the OP, it seems pretty much that it:
Uses the ipTables based on the OS version
Creates a script header based on the UID's for all the applications
that need WiFi/Data
Should be getting the list of installed apps on the device along with
any hidden apps etc from the package manager.
And again executes scripts based on user selection for black list and
overrides the existing rules in the ipTable with the user desired
rules.
Pretty sure though must have been quite hard to code all of that..Sounds much easier in the form of bullet points.
Hope this helps you somewhat.
P.S: If you do figure out something please post an updated answer, would like to know how did you make it work
Update: Make sure you have the neccessary permissions for setting the WiFi/Data on/off in your application manifest. NOT the test apk manifest. IT HAS TO BE THE APPLICATION MANIFEST ITSELF.
There is this library which might help you. Its an extension to solo. http://adventuresinqa.com/2014/02/17/extsolo-library-to-extend-your-robotium-test-automation/
After spending hours trying to do it similar to user2511882s solution, I still had an exception, because of missing permissions (yes the "modify system settings" permission was activated).
I ended up doing it with UI automator:
public static void setAirplaneMode(boolean enable)
{
if ((enable ? 1 : 0) == Settings.System.getInt(getInstrumentation().getContext().getContentResolver(),
Settings.Global.AIRPLANE_MODE_ON, 0))
{
return;
}
UiDevice device = UiDevice.getInstance(getInstrumentation());
device.openQuickSettings();
// Find the text of your language
BySelector description = By.desc("Airplane mode");
// Need to wait for the button, as the opening of quick settings is animated.
device.wait(Until.hasObject(description), 500);
device.findObject(description).click();
getInstrumentation().getContext().sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
}
You will need the ACCESS_NETWORK_STATE in your androidTest manifest file:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Don't forget to disable it after the test.
If you have other languages than english, you need to change "Airplane mode" to the text of your language. As I have several translations, I read it from a ressource string.
There is great library from LinkedIn Test Butler, you can enable, disable both WiFi and mobile data by simply calling:
TestButler.setGsmState(false);
TestButler.setWifiState(false);
The main advantage of this library is that it does not require any permission in your manifest, for more details please refer to project website:
https://github.com/linkedin/test-butler
Sorry if I'm oversimplifying this, but what about just putting the phone/emulator in airplane mode? Through the actual user interface. That's what I do to test offline cases.
Here is a solution that uses UiAutomator to enable or disable "Aeroplane mode" from the drop-down status bar, which turns off all networking if enabled. It works on most Android OS versions, and it uses parts of the answer from user Aorlinn (12 October 2019).
app/build.gradle
dependencies {
androidTestImplementation "androidx.test.uiautomator:uiautomator:2.2.0"
}
AndroidManifest.xml
No extra permissions are needed, not even <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> , because UiAutomator is only clicking on a button in the status bar UI. Therefore the app does not need direct access to modify the device's Wifi or mobile data settings.
Kotlin
import android.os.Build
import android.provider.Settings
import androidx.test.espresso.matcher.ViewMatchers.assertThat
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObjectNotFoundException
import androidx.test.uiautomator.Until
import org.hamcrest.Matchers.`is`
import org.junit.Assert
import org.junit.Assume.assumeNoException
import org.junit.Assume.assumeThat
import java.io.IOException
private var airplaneModeOn = 0
private val OFF = 0
private val ON = 1
private var airplaneModeButtonPosition: Point? = null
/**
* Turn off the Internet connectivity by switching "Aeroplane mode" (flight mode) on. From:
* https://stackoverflow.com/questions/27620976/android-instrumentation-test-offline-cases#68956544
*/
#Test
fun checkAppWillWork_withNoInternetConnection() {
try {
airplaneModeOn = getCurrentAirplaneModeSetting()
} catch (e: Exception) {
e.printStackTrace()
assumeNoException("Cannot retrieve device setting of 'Airplane Mode'. Aborting the test.", e)
}
// Check that Airplane mode is off
assertThat(airplaneModeOn, `is`(OFF))
// Press the "Aeroplane mode" button on the 'Quick Settings' panel
toggleAeroplaneModeButton()
// Verify Airplane mode is on
assumeThat("Cannot change the 'Aeroplane Mode' setting. Test aborted.", airplaneModeOn, `is`(ON))
assertThat(airplaneModeOn, `is`(ON))
// Do some tests when the Internet is down
// Note: Switch the Internet back on in cleanup()
}
/**
* Turn the Internet connectivity on or off by pressing the "Aeroplane mode" button. It opens the
* status bar at the top, then drags it down to reveal the buttons on the 'Quick Settings' panel.
*/
#Throws(UiObjectNotFoundException::class)
private fun toggleAeroplaneModeButton() {
try {
airplaneModeOn = getCurrentAirplaneModeSetting()
} catch (e: SecurityException) {
e.printStackTrace()
Assert.fail()
} catch (e: IOException) {
e.printStackTrace()
Assert.fail()
}
// Open the status bar at the top; drag it down to reveal the buttons
val device = UiDevice.getInstance(getInstrumentation())
device.openQuickSettings()
// Wait for the button to be visible, because opening the Quick Settings is animated.
// You can use any string here; You only need a time delay wait here.
val description = By.desc("AeroplaneMode")
device.wait(Until.hasObject(description), 2000)
// Search for and click the button
var buttonClicked = clickObjectIfFound(device,
"Aeroplane mode", "Airplane mode", "機内モード", "Modo avión")
if (!buttonClicked) {
// Swipe the Quick Panel window to the LEFT, if possible
val screenWidth = device.displayWidth
val screenHeight = device.displayHeight
device.swipe((screenWidth * 0.80).toInt(), (screenHeight * 0.30).toInt(),
(screenWidth * 0.20).toInt(), (screenHeight * 0.30).toInt(), 50)
buttonClicked = clickObjectIfFound(device,
"Aeroplane mode", "Airplane mode", "機内モード", "Modo avión")
}
if (!buttonClicked) {
// Swipe the Quick Panel window to the RIGHT, if possible
val screenWidth = device.displayWidth
val screenHeight = device.displayHeight
device.swipe((screenWidth * 0.20).toInt(), (screenHeight * 0.30).toInt(),
(screenWidth * 0.80).toInt(), (screenHeight * 0.30).toInt(), 50)
clickObjectIfFound(device,
"Aeroplane mode", "Airplane mode", "機内モード", "Modo avión")
}
// Wait for the Internet to disconnect or re-connect
device.wait(Until.hasObject(description), 6000)
// Close the Quick Settings panel
getInstrumentation().context
.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
// Verify change in device settings
try {
airplaneModeOn = getCurrentAirplaneModeSetting()
} catch (e: SecurityException) {
e.printStackTrace()
Assert.fail()
} catch (e: IOException) {
e.printStackTrace()
Assert.fail()
}
}
/**
* On Android 8/9, use an 'adb shell' command to retrieve the "Airplane Mode" setting, like this:
*
*
* `adb shell settings list global | grep airplane_mode_on ==> "airplane_mode_on=0"`
*
*
* (But note that grep is not available on the Android shell. See guidance URLs below)
*
*
* * https://www.reddit.com/r/tasker/comments/fbi5ai/psa_you_can_use_adb_to_find_all_the_settings_that/
* * https://stackoverflow.com/questions/33970956/test-if-soft-keyboard-is-visible-using-espresso
*
*
* On all other Android OS versions, use `Settings.System.getInt()` to retrieve the "Airplane Mode" setting.
* It sets `airplaneModeOn = 1 (true)` or `airplaneModeOn = 0 (false)`
*
* #throws IOException if `executeShellCommand()` didn't work
* #throws SecurityException if `Settings.System.getInt()` didn't work
*/
#Throws(IOException::class, SecurityException::class)
private fun getCurrentAirplaneModeSetting(): Int {
if (Build.VERSION.SDK_INT in 26..28 /*Android 8-9*/) {
val shellResponse = UiDevice
.getInstance(getInstrumentation())
.executeShellCommand("settings list global")
airplaneModeOn = when {
shellResponse.contains("airplane_mode_on=1") -> 1
shellResponse.contains("airplane_mode_on=0") -> 0
else -> throw IOException("Unsuitable response from adb shell command 'settings list global'")
}
} else {
// Oddly this causes a SecurityException on Android 8,9 devices
airplaneModeOn = Settings.System.getInt(
getInstrumentation().context.contentResolver,
Settings.Global.AIRPLANE_MODE_ON,
0)
}
return airplaneModeOn
}
/**
* Make UiAutomator search for and click a single button, based on its text label.
*
* Sometimes multiple buttons will match the required text label. For example,
* when Airplane mode is switched on, "Mobile data Aeroplane mode" and "Aeroplane mode"
* are 2 separate buttons on the Quick Settings panel, on Android 10+.
* For Aeroplane mode, always click on the last matched item.
*
* #param textLabels You must supply the language variants of the button that you want to click,
* for example "Cancel" (English), "Cancelar" (Spanish), "취소" (Korean)
* #return True if a button was found and clicked, otherwise return false
*/
private fun clickObjectIfFound(device: UiDevice, vararg textLabels: String): Boolean {
for (languageVariant in textLabels) {
val availableButtons = device.findObjects(By.text(languageVariant))
if (availableButtons.size >= 1) {
if (airplaneModeButtonPosition == null) {
availableButtons[availableButtons.size - 1].click()
airplaneModeButtonPosition = availableButtons[availableButtons.size - 1].visibleCenter
return true
} else {
// Use the stored position to avoid clicking on the wrong button
for (button in availableButtons) {
if (button.visibleCenter == airplaneModeButtonPosition) {
button.click()
airplaneModeButtonPosition = null
return true
}
}
}
}
}
return false
}
#After
fun cleanup() {
// Switch the Internet connectivity back on, for other tests.
if (airplaneModeOn == ON) {
toggleAeroplaneModeButton()
assertThat(airplaneModeOn, `is`(OFF))
}
}
You will have to adjust the string "Aeroplane mode" if your device is in a different language to English. For example, you could check for about 70 language translations here: https://github.com/aosp-mirror/platform_frameworks_base/search?q=global_actions_toggle_airplane_mode
Due to #user2511882 answer you can use Application context instead of Activity in Android X Test via:
internal fun switchWifi(value: Boolean) {
val wifiManager = ApplicationProvider.getApplicationContext<YourApplicationClass>().getSystemService(Context.WIFI_SERVICE) as WifiManager
wifiManager.isWifiEnabled = value}
Consider that this approach only works on API <= 28. There are other approaches like using UI Automator or API > 28
Related
How can I go to recent apps menu and How to select a particular app from the recents by using Espresso Android Instrumentation Test
Since selecting app from recent apps menu, it need controller over device. I think it can't be done with Espresso alone.
But you can achieve this by using android uiautomator.
fun selectAppFromRecentApps(appTitle:String){
mDevice.pressHome()
mDevice.pressRecentApps()
var uiSelector = UiSelector().className("android.widget.ScrollView") //scroll view listing all recent apps
var count = mDevice.findObject(uiSelector).childCount
for (i in 0 until count step 1) {
val child = UiScrollable(uiSelector.childSelector(UiSelector().resourceId("com.android.systemui:id/task_view_bar").instance(i))).getChild(UiSelector().resourceId("com.android.systemui:id/title")) // app framelayout
val text = child.text
if (text == appTitle) {
child.click()
break
}
if(i==count-1){
throw RuntimeException("App : "+ appTitle +" not found in
recent apps")
}
}
}
Unfortunately VIGNESHs answer did not work for me. I expect different devices to have different implementations of the overview view, or probably it's the Android version.
Anyway the following worked for me on different devices:
getInstrumentation().waitForIdleSync();
mDevice.pressHome();
mDevice.pressRecentApps();
mDevice.waitForIdle();
// If the application is listed, there needs to be an element with a content description
// containing the package name
if (!mDevice.wait(Until.hasObject(By.descStartsWith(getTargetContext().getPackageName())), 1000))
{
fail("Overview did not open");
}
// As the app to be tested was the last one opened, we can simply press the button again.
mDevice.pressRecentApps();
// Wait until the activity under test is back.
// If you skip that, you might be to early, doing further tests.
mDevice.wait(Until.hasObject(By.pkg(getTargetContext().getPackageName())), 1000);
getInstrumentation().waitForIdleSync();
Background
I've noticed that in WifiManager class there is a function called addNetwork, that might be useful if I want to restore or save networks information (network name AKA SSID, together with the password and the type), so that I could also connect to it.
The problem
I can't find much information about how to do such a thing. I've seen various examples on StackOverflow, and if I target Android API 28 (or below), I indeed succeed to make it add a network and even connect to it.
When targeting Android 29 (Android Q), however, it fails to add the network.
What I've found
Since I'm trying on Pixel 2 with Android Q beta 4, I think that maybe it's because addNetwork is deprecated, so the docs even say so, and that if I target Android Q, it won't work, and indeed it doesn't work:
Compatibility Note: For applications targeting Build.VERSION_CODES.Q
or above, this API will always return -1.
The way it seems it should work up till Android Q (excluding), is by preparing WifiConfiguration and adding it. Later I can also connect to it if I wish. On Android Q, it seems it was replaced by WifiNetworkSuggestion, but it doesn't seem like it's about adding a network at all:
The Network Suggestion object is used to provide a Wi-Fi network for
consideration when auto-connecting to networks. Apps cannot directly
create this object, they must use
WifiNetworkSuggestion.Builder#build() to obtain an instance of this
object.
Apps can provide a list of such networks to the platform using
WifiManager#addNetworkSuggestions(List).
Here's my current code, for pre-Android-Q
#WorkerThread
fun addNetwork(context: Context, networkName: String, networkPassword: String? = null, keyMgmt: Int = WifiConfiguration.KeyMgmt.NONE) {
val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
val conf = WifiConfiguration()
conf.SSID = "\"$networkName\""
conf.preSharedKey = if (networkPassword.isNullOrEmpty()) "" else "\"$networkPassword\""
conf.allowedKeyManagement.set(keyMgmt)
when (keyMgmt) {
WifiConfiguration.KeyMgmt.WPA_PSK -> {
//WPA/WPA2
}
WifiConfiguration.KeyMgmt.IEEE8021X -> {
}
WifiConfiguration.KeyMgmt.WPA_EAP -> {
}
WifiConfiguration.KeyMgmt.NONE -> {
if (networkPassword.isNullOrEmpty()) {
//open network
conf.wepKeys[0] = "\"\""
} else {
//wep
conf.wepKeys[0] = "\"" + networkPassword + "\""
conf.wepTxKeyIndex = 0
conf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40)
}
}
}
if (networkPassword.isNullOrEmpty()) {
//open network
conf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE)
} else {
}
wifiManager.isWifiEnabled = true
while (!wifiManager.pingSupplicant()) {
Log.d("AppLog", "waiting to be able to add network")
}
val networkId = wifiManager.addNetwork(conf)
if (networkId == -1)
Log.d("AppLog", "failed to add network")
else {
wifiManager.enableNetwork(networkId, false)
Log.d("AppLog", "success to add network")
}
}
Seems it requires only these permissions:
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
But in any case, this works as long as you don't target Android Q (API 29) and above. When you target it, I indeed always get "-1" as a result, meaning it fails.
I've also found an issue on the issue tracker (here and I wrote about it here), telling about someone that needs the API back, but I'm not sure it's about adding a network.
Looking at WifiNetworkSuggestion, I don't see that it has as many things to set as WifiConfiguration via its builder, so this is another reason for why I suspect it's not about adding a network.
But I tried anyway. Here's the code I've tried, for example, to add a normal WPA network:
#WorkerThread
fun addNetworkAndroidQ(context: Context, networkName: String, networkPassword: String? = null) {
val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
val list = ArrayList<WifiNetworkSuggestion>()
val builder = WifiNetworkSuggestion.Builder().setSsid(networkName)
if (!networkPassword.isNullOrEmpty())
builder.setWpa2Passphrase(networkPassword)
list.add(builder.build())
val result = wifiManager.addNetworkSuggestions(list)
if (result == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS)
Log.d("AppLog", "success")
else Log.d("AppLog", "failed")
}
When running (I gave it my Wifi network details, after making the OS forget about it), it says it succeeded, but nothing occurred on the OS's Wifi settings. The network doesn't exist there with the password I've added. So I really don't get what it did...
After a few long seconds, I've noticed a notification asking me if it's ok to connect to the suggested networks made by the app:
But still when I chose that I accept, it didn't do anything, as before.
I tried to make another POC, thinking I might have done it incorrectly, but then it didn't even show the notification. Since I think this whole behavior is a bug, I've reported about it here.
Not only that, but I've found out that if indeed it is supposed to add a network one way or another, it still has some serious restrictions, such as max added networks (here) and being removed upon uninstall of the app (here)
The questions
How should Android Q be handled exactly? Is there really no API anymore to add a network?
If WifiNetworkSuggestion is not about adding a network, what is it really used for exactly?
Since I'm not familiar enough with the tidbits of adding a network, is my code correct about all possible ways to add a network? I ask this because someone wrote here that people should enable Wifi and make sure pingSupplicant returns true. Is it true? Or would it be enough to just call addNetwork ?
If it's now impossible to add a network using the normal API, is there maybe a solution by using a rooted device instead? Maybe some adb command?
EDIT: Not sure how to do it officially, but using adb, you might be able to add Wifi-networks on Android 11 . Need to check adb shell cmd wifi help .
I stuck with same issue, but somehow I reached a reproducible state for connecting a desired network and I want to share my findings it may helps.
As a summary:
You have to disable all auto connection before applying WifiNetworkSuggestion logic
For more details, Please read the following:
I used the following code (Similar to what you use):
private fun connectUsingNetworkSuggestion(ssid: String, password: String) {
val wifiNetworkSuggestion = WifiNetworkSuggestion.Builder()
.setSsid(ssid)
.setWpa2Passphrase(password)
.build()
// Optional (Wait for post connection broadcast to one of your suggestions)
val intentFilter =
IntentFilter(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION);
val broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (!intent.action.equals(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION)) {
return
}
showToast("Connection Suggestion Succeeded")
// do post connect processing here
}
}
registerReceiver(broadcastReceiver, intentFilter)
lastSuggestedNetwork?.let {
val status = wifiManager.removeNetworkSuggestions(listOf(it))
Log.i("WifiNetworkSuggestion", "Removing Network suggestions status is $status")
}
val suggestionsList = listOf(wifiNetworkSuggestion)
var status = wifiManager.addNetworkSuggestions(suggestionsList)
Log.i("WifiNetworkSuggestion", "Adding Network suggestions status is $status")
if (status == WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_DUPLICATE) {
showToast("Suggestion Update Needed")
status = wifiManager.removeNetworkSuggestions(suggestionsList)
Log.i("WifiNetworkSuggestion", "Removing Network suggestions status is $status")
status = wifiManager.addNetworkSuggestions(suggestionsList)
}
if (status == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS) {
lastSuggestedNetwork = wifiNetworkSuggestion
lastSuggestedNetworkSSID = ssid
showToast("Suggestion Added")
}
}
So here are the steps:
Install fresh version / Or remove all suggestion you added before
Make sure that you forgot all surrounding networks so your device won't auto-connect
Add wifi network suggestions list
Go to Wifi Settings to scan networks Or wait until next scan is running
A notification prompt will appear :
6. When you Press "Yes" the system will auto-connect with it via your app and internet will work normally. See the following:
Please note the following:
If you disconnect the network from Wifi Settings (i.e press disconnect bin icon in the following image) your network will be blocked for 24 hours from auto-connect even if you removed the suggested network using wifiManager.removeNetworkSuggestions(listOf(it)) and add it again. And even if you uninstall and install your app again
Unfortunately, this is limitation added by Android System as described here:
If the user uses the Wi-Fi picker to explicitly disconnect from one of the network suggestions when connected to it, then that network is blacklisted for 24 hours. During the blacklist period, that network will not be considered for auto-connection, even if the app removes and re-adds the network suggestion corresponding to the network.
If you uninstall the application while connected to suggested WiFi, the system will close the connection automatically.
In case you have multiple suggestion you can priorities them by using WifiNetworkSuggestion.Builder().setPriority(<Priority Integer>) as mentioned here:
Specify the priority of this network among other network suggestions provided by the same app (priorities have no impact on suggestions by different apps). The higher the number, the higher the priority (i.e value of 0 = lowest priority).
In case you pressed "No" in notification prompt, you can change it from (Settings > Apps & notifications > Special App access > Wi-Fi Control > App name) as described here:
A user declining the network suggestion notification removes the CHANGE_WIFI_STATE permission from the app. The user can grant this approval later by going into the Wi-Fi control menu (Settings > Apps & notifications > Special App access > Wi-Fi Control > App name).
I wish I had answers to all of your questions because I'm currently struggling with similar issues.
After many hours I was finally able to connect to the desired network using this approach:
val wifiNetworkSpecifier = WifiNetworkSpecifier.Builder()
.setSsid(ssid)
.setWpa2Passphrase(passphrase)
.setBssid(mac)
.build()
val networkRequest = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.setNetworkSpecifier(wifiNetworkSpecifier)
.build()
val connectivityManager = applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager?
connectivityManager?.requestNetwork(networkRequest, ConnectivityManager.NetworkCallback())
You can receive a whole host of events through the ConnectivityManager.NetworkCallback().
Looks like they've added support in Android 11(API 30) for adding network configuration that persists outside of the application scope and is saved as a system network configuration just like it was done with the deprecated WiFiManager method addNetwork. All you need to do is to use ACTION_WIFI_ADD_NETWORKS to show a system dialog that asks a user if he wants to proceed with adding a new Wifi suggestion to the system. This is how we start that dialog:
// used imports
import android.provider.Settings.ACTION_WIFI_ADD_NETWORKS
import android.provider.Settings.EXTRA_WIFI_NETWORK_LIST
import android.app.Activity
import android.content.Intent
import android.net.wifi.WifiNetworkSuggestion
// show system dialog for adding new network configuration
val wifiSuggestionBuilder = WifiNetworkSuggestion.Builder()
.setSsid("network SSID")
.build()
val suggestionsList = arraylistOf(wifiSuggestionBuilder)
val intent = new Intent(ACTION_WIFI_ADD_NETWORKS)
intent.putParcelableArrayListExtra(EXTRA_WIFI_NETWORK_LIST, suggestionsList);
activity.startActivityForResult(intent, 1000)
The dialog looks like this:
And then we just need to handle a result in onActivityResult method like this:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == 1000) {
if (resultCode == Activity.RESULT_OK) {
// network succesfully added - User pressed Save
} else if (resultCode == Activity.RESULT_CANCELED) {
// failed attempt of adding network to system - User pressed Cancel
}
}
}
But as I've tested this code on Android devices that have older Android versions(lower then API30) installed I've got a crash every time I want it to show that dialog for adding a new network configuration. This is the crash:
java.lang.RuntimeException: Unable to start activity: android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.settings.WIFI_ADD_NETWORKS (has extras) }
Looks like the new way is not back-supported out of the box. So, for API30 we can use a new Intent action, for API 28 and below we can still use the old way of adding Networks, but for API29 we have some kind of gray area where I was not able to find a good solution yet. If anyone has an idea what else to do please share it with me. ;)
#Sebastian Helzer's answer works for me. I use java in my application. This may help java users...
WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier.Builder()
.setSsid(ssid)
.setWpa2Passphrase(password)
.build();
NetworkRequest networkRequest = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.setNetworkSpecifier(wifiNetworkSpecifier)
.build();
ConnectivityManager connectivityManager = (ConnectivityManager)this.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
connectivityManager.requestNetwork(networkRequest, new ConnectivityManager.NetworkCallback());
I am trying to trace the wifi settings code. My intention is to know the flow from application to kernel layer after toggling the WIFI button OFF to ON.
While we go to Settings page in Android, toggle the WLAN(WIFI) button, then
your wifi should be enabled.
I found that this page corresponds to WifiSettings.java. In this file, while you toggle the button from OFF to ON:
private void updateWifiState(int state) {
getActivity().invalidateOptionsMenu();
switch (state) {
case WifiManager.WIFI_STATE_ENABLING:
addMessagePreference(R.string.wifi_starting);
break;
}
mLastInfo = null;
mLastState = null;
mScanner.pause();
}
This function will be called.
I then go to check WifiManager.java. I found:
/**
* Wi-Fi is currently being enabled. The state will change to
{#link#WIFI_STATE_ENABLED}
if it finishes successfully.
*
* #see #WIFI_STATE_CHANGED_ACTION
* #see #getWifiState()
*/
public static final int WIFI_STATE_ENABLING = 2;
However, after this, I did not really understand how to dig deeper in tracing the flow.
You should look at WifiEnabler.java, since the code you mentioned just shows some string.
In WifiEnabler::onCheckChanged(), you can see that mWifiManager.setWifiEnabled() is called.
After that, you may look at WifiManager.java and other related files in that directory. There is a state machine and you should trace the state transitions.
Generally, Android is using wpa_supplicant, which is nearly the same as Linux. The configuration file is at /data/misc/wifi/wpa_supplicant.conf, and is generated by Android. After you toggle the wifi switch, Android starts wpa_supplicant and communicates with it. The wpa_supplicant is in charge of scanning and connecting to wifi station.
This is for the Verizon LTE version of the Samsung Galaxy Nexus.
I am tasked with writing a tiny app that will effectively disable/enable 4G capability. This can be done manually via settings > mobile network > network mode and choosing either LTE/CDMA (4g enabled) or CDMA (3g only).
I have not tried anything yet because Android development isn't my strong suit. I am looking for guidance... examples, code samples etc. I am assuming this should almost be a one-liner, but it has been my experience that with Android development nothing is as simple as it appears.
Any help will be greatly appreciated.
There is a preference in the Settings.Secure class that is hidden from the SDK:
/**
* The preferred network mode 7 = Global
* 6 = EvDo only
* 5 = CDMA w/o EvDo
* 4 = CDMA / EvDo auto
* 3 = GSM / WCDMA auto
* 2 = WCDMA only
* 1 = GSM only
* 0 = GSM / WCDMA preferred
* #hide
*/
public static final String PREFERRED_NETWORK_MODE =
"preferred_network_mode";
You could use Reflection on this or just localize the constant to your project. The problem with this is that you cannot change the value of this setting (as with all secure settings), you can only read it. The aforementioned values are not the only possible ones, there are actually a few more located in com.android.internal.telephony.RILConstants, which is again hidden from the SDK and would require Reflection to access.
There is another hidden method in TelephonyManager, but again it is read only there is no other method for setting this constant. This would tell you exactly what you want to know, whether the device is set to "LTE/ CDMA" (LTE_ON_CDMA_TRUE) or "CDMA only" (LTE_ON_CDMA_FALSE):
/**
* Return if the current radio is LTE on CDMA. This
* is a tri-state return value as for a period of time
* the mode may be unknown.
*
* #return {#link Phone#LTE_ON_CDMA_UNKNOWN}, {#link Phone#LTE_ON_CDMA_FALSE}
* or {#link Phone#LTE_ON_CDMA_TRUE}
*
* #hide
*/
public int getLteOnCdmaMode() {
try {
return getITelephony().getLteOnCdmaMode();
} catch (RemoteException ex) {
// Assume no ICC card if remote exception which shouldn't happen
return Phone.LTE_ON_CDMA_UNKNOWN;
} catch (NullPointerException ex) {
// This could happen before phone restarts due to crashing
return Phone.LTE_ON_CDMA_UNKNOWN;
}
}
From my research you could not make such an application without root access and using something like setprop from the command line, but even then you may need to restart the entire Telephony process in order for this setting to take effect.
Finally, if you are still interested see com.android.phone.Settings to see how the system handles this toggle. It is rather elaborate, and as I mentioned would require permissions that a normal Android application would not be granted.
I'm also interested in changing the settings WCDMA-only, WCDMA/LTE, ...
I found the way to change Settings.secure.* with root privilege as is shown the below.
new ExecuteAsRootBase() {
#Override
protected ArrayList<String> getCommandsToExecute() {
ArrayList<String> cmds = new ArrayList<String>();
cmds.add("su -c 'chmod 755 "+mySqlite+"'");
cmds.add("echo \"UPDATE secure SET value='"+ value +"' WHERE name='"+ key +"'; \" | "+mySqlite+" /data/data/com.android.providers.settings/databases/settings.db");
//TODO: SQL injection can be done!!!
return cmds;
}
}.execute();
ExecuteAsRootBase is introduced here, and mySqlite is "/data/data/"+context.getPackageName()+"/files/sqlite3" where sqlite3 is put in advance.
However, it seems that we have to call com.android.internal.telephony.Phone.setPreferredNetworkType() for switching (WCDMA only<=>WCDMA/LTE) after setting Settings.secure.PREFERRED_NETWORK_MODE.
My phone (even set Settings.secure.PREFERRED_NETWORK_MODE = 2) attached to LTE network...
All the other answers are correct that this requires access to Settings.Secure. Take a look at how the phone app handles this setting https://github.com/dzo/packages_apps_phone/blob/master/src/com/android/phone/Use2GOnlyCheckBoxPreference.java
or take a look at the Toggle2G app source:
https://github.com/TheMasterBaron/Toggle-2G
http://developer.android.com/reference/android/provider/Settings.System.html
Aside from writing the code in your Activity.java, you will probably have to ask for permission to access these settings in the AndroidManifest.xml. So it's annoying but should be simple enough.
Is it possible to consistently detect if an Activity has hardware acceleration enabled when it is created? I'm worried that users of my library will enable it through the manifest when they shouldn't, by not specifically disabling it for my Activity (as I instruct them to do.)
The only solid information I can find (http://android-developers.blogspot.com/2011/03/android-30-hardware-acceleration.html) says that I can query View.isHardwareAccelerated() and Canvas.isHardwareAccelerated(). However, for my purposes, I would like to ensure it is off when my library's Activity is shown. So far, I can't get anything to report a consistent yes/no when it is on or off. I tried hacking in a dummy view, setting it to my activity and then testing it, but it always returns false. Also, I tried testing Window.getAttributes( ).flags, but they aren't showing it either.
I am testing this because the hardware accelerated draw path for my library doesn't function correctly, and there doesn't seem like there is any way to fix it.
Try FLAG_HARDWARE_ACCELERATED in flags in ActivityInfo for the activity, which you would get from PackageManager via getActivityInfo().
I'm new in Android so I was stuck even with the clues given in the answer above.. went to search around and found this code somewhere in the sea of Google. Hope it helps someone.
/**
* Returns true if the given Activity has hardware acceleration enabled
* in its manifest, or in its foreground window.
*
* TODO(husky): Remove when initialize() is refactored (see TODO there)
* TODO(dtrainor) This is still used by other classes. Make sure to pull some version of this
* out before removing it.
*/
public static boolean hasHardwareAcceleration(Activity activity) {
// Has HW acceleration been enabled manually in the current window?
Window window = activity.getWindow();
if (window != null) {
if ((window.getAttributes().flags
& WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0) {
return true;
}
}
// Has HW acceleration been enabled in the manifest?
try {
ActivityInfo info = activity.getPackageManager().getActivityInfo(
activity.getComponentName(), 0);
if ((info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
return true;
}
} catch (PackageManager.NameNotFoundException e) {
Log.e("Chrome", "getActivityInfo(self) should not fail");
}
return false;
}