Always getting -1 in adding new wifiConfiguration to the wifiiManager - android

I am creating a list of in-range wifis and show it to the user. I want to the user can select each of the items in the list and insert the password in order to connect to the selected SSID.
I wrote this method for wifi connection:
private WifiConfiguration wifiConf; /* WifiConfiguration object */
private WifiManager wifiMgr; /* WifiManager object */
private WifiInfo wifiInfo; /* WifiInfo object */
public boolean connectToSelectedNetwork(String networkSSID, String networkPassword, String securityType) {
int networkId;
int SecurityProtocol;
if (securityType.contains("WEP")) {
SecurityProtocol = 1;
Log.i(TAG, "Security: WEP");
} else if (securityType.contains("WPA2")) {
Log.i(TAG, "Security: WPA2");
SecurityProtocol = 2;
} else if (securityType.contains("WPA")) {
Log.i(TAG, "Security: WPA");
SecurityProtocol = 3;
} else {
Log.i(TAG, "Security: OPEN");
SecurityProtocol = 4;
}
// Clear wifi configuration variable
clearWifiConfig();
// Sets network SSID name on wifiConf
wifiConf.SSID = "\"" + networkSSID + "\"";
Log.i(TAG, "SSID Received: " + wifiConf.SSID);
switch (SecurityProtocol) {
// WEP "security".
case WEP:
wifiConf.wepKeys[0] = "\"" + networkPassword + "\"";
wifiConf.wepTxKeyIndex = 0;
wifiConf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
wifiConf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);
break;
// WAP security. We have to set preSharedKey.
case WPA2:
wifiConf.preSharedKey = "\"" + networkPassword + "\"";
wifiConf.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
wifiConf.allowedProtocols.set(WifiConfiguration.Protocol.RSN); // For WPA2
wifiConf.allowedProtocols.set(WifiConfiguration.Protocol.WPA); // For WPA
wifiConf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
wifiConf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
wifiConf.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
wifiConf.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
wifiConf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
wifiConf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
break;
case WPA:
wifiConf.preSharedKey = "\"" + networkPassword + "\"";
// Network without security.
case OPEN_NETWORK:
wifiConf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
break;
}
// Add WiFi configuration to list of recognizable networks
if ((networkId = wifiMgr.addNetwork(wifiConf)) == -1) {
Log.i("TAG", "Failed to add network configuration!");
return false;
}
// Disconnect from current WiFi connection
if (!disconnectFromWifi()) {
Log.i("TAG", "Failed to disconnect from network!");
return false;
}
// Enable network to be connected
if (!wifiMgr.enableNetwork(networkId, true)) {
Log.i("TAG", "Failed to enable network!");
return false;
}
// Connect to network
if (!wifiMgr.reconnect()) {
Log.i("TAG", "Failed to connect!");
return false;
}
return true;
}
But when I call this function in order to connect to the selected wifi I always get false! I debugged it a lot of times and it goes into first if in this method And doesn't connect to the wifi.
Please help me with your answers.
Thank you.

addNetwork() returns -1 on failure.
One common reason for it to fail is when the network was previously configured by the user (through the wifi setting screen) or programmatically, via an app with different Unix User ID (i.e. UID). Recall that every app is associated with a UID.
If the same UID calls addNetwork() for the second time, it will be allowed to override the configuration and the function will succeed.
A similar related behavior is with regards to removing a configuration via the removeNetwork() API. Only the app with the UID that added the configuration can remove it.
This behavior makes sense since you wouldn't want a malicious (or just buggy) app, to override or remove configurations done by either the user or other apps.
So far so good, but there's a complication - when an app is reinstalled (read: uninstalled and then installed again), the OS will provide it with a different UID. Now the same app which previously added the configuration, following the reinstall, can no longer remove or update it. Ouch!
It would have been nice to allow the same application ID to be able to update/remove a network. Oh well.

Related

How to programmatically change password of a saved wifi network in Android (API level 23, Android 6.0)

Premise:
I'm currently work on an Android App (API level 23, Android 6.0) that connect with a device via Wi-Fi and uses UDP packets to communicate. I'm able to change the device Wi-Fi password using a particular command. This works fine.
Target:
What I'm tring to programmatically do is:
search Wi-Fi generated from the device
connect to the device
send the command to change the password
reconnect to the device using the new password
I'm able to connect the first time (steps 1,2,3) using code like this:
private void connect(String ssid, String password) {
WifiConfiguration conf = new WifiConfiguration();
conf.SSID = String.format("\"%s\"", ssid);
conf.preSharedKey = String.format("\"%s\"", password);
netId = mWifiManager.addNetwork(conf);
mWifiManager.saveConfiguration();
mWifiManager.disconnect();
mWifiManager.enableNetwork(netId, true);
mWifiManager.reconnect();
}
Additional info:
In the Manifest file I declared these permissions:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
The problem:
If I try to use the same method to connect after the change of the password, I'm not able to achive the connection because (I think) Android remembers the previous password.
If I try to use updateNetwork(conf) instead of addNetwork(conf), I don't notice any difference.
I've tried to remove or disable in some ways the saved network before try to connect again, but unsaccesfully.
mWifiManager.removeNetwork(netId)
returns false (I have no idea why it fails)
mWifiManager.disableNetwork(netId);
returns true but it appears to have no effects
If I use the Android settings to change password, all works fine... but I want to change the saved password programmatically.
Any help is very appreciated
After several attempts, I currently have no problems following the instructions I report below.
Before connecting to a Wi-Fi network it is important to check the list of previously saved networks. If there is no network with the same SSID it is possible to create a new configuration, otherwise it is necessary to modify the existing one.
public void connect(String ssid, String password) {
WifiConfiguration wifiConf = null;
WifiConfiguration savedConf = null;
//existing configured networks
List<WifiConfiguration> list = mWifiManager.getConfiguredNetworks();
if(list!=null) {
for( WifiConfiguration i : list ) {
if (i.SSID != null && i.SSID.equals("\"" + ssid + "\"")) {
Log.d(TAG, "existing network found: " + i.networkId + " " + i.SSID);
savedConf = i;
break;
}
}
}
if(savedConf!=null) {
Log.d(TAG, "coping existing configuration");
wifiConf = savedConf;
} else {
Log.d(TAG, "creating new configuration");
wifiConf = new WifiConfiguration();
}
wifiConf.SSID = String.format("\"%s\"", ssid);
wifiConf.preSharedKey = String.format("\"%s\"", password);
int netId;
if(savedConf!=null) {
netId = mWifiManager.updateNetwork(wifiConf);
Log.d(TAG, "configuration updated " + netId);
} else {
netId = mWifiManager.addNetwork(wifiConf);
Log.d(TAG, "configuration created " + netId);
}
mWifiManager.saveConfiguration();
mWifiManager.disconnect();
mWifiManager.enableNetwork(netId, true);
mWifiManager.reconnect();
}
Android after version 6 does not allow any application to modify Wi-Fi networks unless it has been created by the application itself. In addition, it does not allow adding Wi-Fi networks with the same name as others already configured previously. This is very curious because when you configure Wi-Fi networks manually from the settings, it does allow it.
After thinking for a long time about a possible solution it occurred to me that perhaps the Wifi network could be added with another name and once added, change it to the desired name. I have tried it and it works. It would be something like the following:
int networkId = -1;
// Find my Wifi
List<WifiConfiguration> configuredWifis = wifiManager.getConfiguredNetworks();
for(WifiConfiguration wifi : configuredWifis)
{
if(wifi.SSID != null && wifi.SSID.equals("\"" + WIFI_SSID + "\"") && wifi.priority == WIFI_PRIORITY)
{
networkId = wifi.networkId;
}
else
{
wifiManager.disableNetwork(wifi.networkId);
wifiManager.removeNetwork(wifi.networkId); // After android 6 it really does not remove the Wifi network
wifiManager.saveConfiguration();
}
}
if(networkId == -1)
{
// If my Wifi is not yet configured then add it
WifiConfiguration conf = new WifiConfiguration();
conf.SSID = "\"" + WIFI_SSID + "\"";
conf.preSharedKey = "\""+ WIFI_PASSW +"\"";
conf.priority = WIFI_PRIORITY;
networkId = wifiManager.addNetwork(conf);
if(networkId == -1) // This ocurs when a wifi with the same name is yet configured. After Android 6 is not possible modify it.
{
Random random = new Random(System.currentTimeMillis());
int randomInteger = random.nextInt(10000);
conf.SSID = "\"" + WIFI_SSID + randomInteger + "\"";
networkId = wifiManager.addNetwork(conf); // Add my wifi with another name
conf.SSID = "\"" + WIFI_SSID + "\"";
conf.networkId = networkId;
networkId = wifiManager.updateNetwork(conf); // After my wifi is added with another name, I change it to the desired name
}
}
// Connect to my wifi
if(wifiManager.getConnectionInfo().getNetworkId() != networkId)
{
wifiManager.disconnect();
wifiManager.enableNetwork(networkId, true);
wifiManager.reconnect();
}

issues with one to many mobile device file transfer in android using wifi direct

I am trying to create an app where one android app can transfer files ( text , video , photo ) to other multiple android devices.initially i thought to use wifi direct in android to share files across multiple devices.
but the problem i am facing with WiFi direct is , its inconsistency in maintaining connection and finding other devices.
1) sometimes app has to wait around 5 mins or more minutes and after that its able to connect.
2) many times after accepting invitation through a dialog from other device, it takes lot of time in changing connection to connected state and till then device is not able to get the IP address of other device.
after experiencing the inconsistency, i thought to drop the idea of using wifi direct. does anyone has a better suggestion to FASTER transfer multiple files from one mobile device to another devices without access points.
Hotspot uses hidden methods which are invoked using Reflection. Essentially, hotspot is an access point to which other people can connect as they connect to normal wifi networks.
As told above it is an access point, hence they are two major functions one needs to support
Creating a hotspot
Connecting to one.
1. Creating a hotspot
/**
* Start AccessPoint mode with the specified
* configuration. If the radio is already running in
* AP mode, update the new configuration
* Note that starting in access point mode disables station
* mode operation
* #param wifiConfig SSID, security and channel details as part of WifiConfiguration
* #return {#code true} if the operation succeeds, {#code false} otherwise
*/
public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
try {
if (enabled) { // disable WiFi in any case
mWifiManager.setWifiEnabled(false);
}
Method method = mWifiManager.getClass().getMethod("setWifiApEnabled", WifiConfiguration.class, boolean.class);
return (Boolean) method.invoke(mWifiManager, wifiConfig, enabled);
} catch (Exception e) {
Log.e(this.getClass().toString(), "", e);
return false;
}
}
Setting up a hotspot with password (WPA2 in the following example)
WifiConfiguration wifiCon = new WifiConfiguration();
wifiCon.SSID = "ssid";
wifiCon.preSharedKey = "password";
wifiCon.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);
wifiCon.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
wifiCon.allowedProtocols.set(WifiConfiguration.Protocol.WPA);
wifiCon.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
try
{
Method setWifiApMethod = wm.getClass().getMethod("setWifiApEnabled", WifiConfiguration.class, boolean.class);
boolean apstatus=(Boolean) setWifiApMethod.invoke(wm, wifiCon,true);
}
catch (Exception e)
{
Log.e(this.getClass().toString(), "", e);
}
2. Connecting to a hotspot
public Boolean connectToHotspot(WifiManager wifiManager, String ssid)
{
this.wifiManager = wifiManager;
WifiConfiguration wc = new WifiConfiguration();
wc.SSID = "\"" +encodeSSID(ssid) +"\"";
wc.preSharedKey = "\"" + generatePassword(new StringBuffer(ssid).reverse().toString()) + "\"";
wifiManager.addNetwork(wc);
List<WifiConfiguration> list = wifiManager.getConfiguredNetworks();
for( WifiConfiguration i : list ) {
if(i!=null && i.SSID != null && i.SSID.equals(wc.SSID))
{
wifiManager.disconnect();
boolean status = wifiManager.enableNetwork(i.networkId, true);
wifiManager.reconnect();
return status;
}
}
return false;
}
Coming to think of it, you may also need list of devices connected to hotspot
/**
* Gets a list of the clients connected to the Hotspot, reachable timeout is 300
* #param onlyReachables {#code false} if the list should contain unreachable (probably disconnected) clients, {#code true} otherwise
* #param finishListener, Interface called when the scan method finishes
*/
public void getClientList(boolean onlyReachables, FinishScanListener finishListener) {
getClientList(onlyReachables, 300, finishListener );
}
/**
* Gets a list of the clients connected to the Hotspot
* #param onlyReachables {#code false} if the list should contain unreachable (probably disconnected) clients, {#code true} otherwise
* #param reachableTimeout Reachable Timout in miliseconds
* #param finishListener, Interface called when the scan method finishes
*/
public void getClientList(final boolean onlyReachables, final int reachableTimeout, final FinishScanListener finishListener) {
Runnable runnable = new Runnable() {
public void run() {
BufferedReader br = null;
final ArrayList<String> resultIPAddr = new ArrayList<String>();
try {
br = new BufferedReader(new FileReader("/proc/net/arp"));
String line;
while ((line = br.readLine()) != null) {
String[] splitted = line.split(" +");
if ((splitted != null) && (splitted.length >= 4)) {
// Basic sanity check
String mac = splitted[3];
if (mac.matches("..:..:..:..:..:..")) {
boolean isReachable = InetAddress.getByName(splitted[0]).isReachable(reachableTimeout);
if (!onlyReachables || isReachable) {
resultIPAddr.add(splitted[0]);
}
}
}
}
} catch (Exception e) {
Log.e(this.getClass().toString(), e.toString());
} finally {
try {
br.close();
} catch (IOException e) {
Log.e(this.getClass().toString(), e.getMessage());
}
}
// Get a handler that can be used to post to the main thread
Handler mainHandler = new Handler(context.getMainLooper());
Runnable myRunnable = new Runnable() {
#Override
public void run() {
finishListener.onFinishScan(result);
}
};
mainHandler.post(myRunnable);
}
};
Thread mythread = new Thread(runnable);
mythread.start();
}
Also you may need to scan for nearby Wifi networks to get the network to connect to.
//This can be done by getting WifiManager's instance from the System.
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
wifiManager.getScanResults();
// The above is an async call and will results are available System will broadcast `SCAN_RESULTS_AVAILABLE` intent and you need to set a `BroadCastReceiver` for it.
// And get the results like this
List<ScanResult> results = wifiManager.getScanResults();
Hope these are all the pointers you need..!!
Wifi direct as a technology is ideal for peer to peer communication.However talking about wifi direct for android , it works well only for a single connection that is only 1-1 use case .It is possible to create a one to many where every device connects to the group owner (Soft AP).But even if u build a one to one and try extend to multi use case you will have problems with certain devices that do no connect perfectly.Devices getting stuck in invited state. Checkout some of the problems listed under problems after downloading this application https://play.google.com/store/apps/details?id=com.budius.WiFiShoot&hl=en
If you want to go for multiple connection it is better to use hotspot .One device ends up being the hotspot and other clients connect to the hotspot .It is being used by plenty of application like xender, zapiya.

Android Device Owner via NFC WiFi-type parameter

What are the possible values for the field 'WiFi Security Type' ?
The documentation does not list possible values.
https://developer.android.com/reference/android/app/admin/DevicePolicyManager.html#EXTRA_PROVISIONING_WIFI_SECURITY_TYPE
I would like to have a list like:
WPA = "wpa"
WPA2 = "wpa2"
WPA2-personal = "wpa2personal"
WPA2-enterprise = "wpa2enterprise"
etc.
I'm not willing to try things out, like 'brute-forcing' what works and what does not work, as every time you try, you'll have to wipe and start over. Wasting at least 15 minutes.
As of Android 5.0.0_r1-5.1.0_r1, the acceptable fields are "NONE", "WPA", and "WEP". It appears that a null or empty value will resolve to "NONE" but I haven't confirmed.
The fields are directly mapped to a WifiConfig class in the ManagedProvisioning project (AOSP).
https://android.googlesource.com/platform/packages/apps/ManagedProvisioning/+/android-5.0.0_r7/src/com/android/managedprovisioning/WifiConfig.java
Note: The security type is defined directly in this class, instead of using constants from the WifiConfiguration class.
enum SecurityType {
NONE,
WPA,
WEP;
}
And this is a snippet of the WifiConfig function (AOSP) showing how the security type is used:
/**
* Adds a new WiFi network.
*/
public int addNetwork(String ssid, boolean hidden, String type, String password,
String proxyHost, int proxyPort, String proxyBypassHosts, String pacUrl) {
if (!mWifiManager.isWifiEnabled()) {
mWifiManager.setWifiEnabled(true);
}
WifiConfiguration wifiConf = new WifiConfiguration();
SecurityType securityType;
if (type == null || TextUtils.isEmpty(type)) {
securityType = SecurityType.NONE;
} else {
securityType = Enum.valueOf(SecurityType.class, type.toUpperCase());
}
// If we have a password, and no security type, assume WPA.
// TODO: Remove this when the programmer supports it.
if (securityType.equals(SecurityType.NONE) && !TextUtils.isEmpty(password)) {
securityType = SecurityType.WPA;
}
wifiConf.SSID = ssid;
wifiConf.status = WifiConfiguration.Status.ENABLED;
wifiConf.hiddenSSID = hidden;
switch (securityType) {
case NONE:
wifiConf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
wifiConf.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
break;
case WPA:
updateForWPAConfiguration(wifiConf, password);
break;
case WEP:
updateForWEPConfiguration(wifiConf, password);
break;
}
updateForProxy(wifiConf, proxyHost, proxyPort, proxyBypassHosts, pacUrl);
int netId = mWifiManager.addNetwork(wifiConf);
if (netId != -1) {
// Setting disableOthers to 'true' should trigger a connection attempt.
mWifiManager.enableNetwork(netId, true);
mWifiManager.saveConfiguration();
}
return netId;
}
Seems we're out of luck if we require credentials for an enterprise network. It's a bit baffling to me since the device owner feature is meant for MDM; connecting to enterprise networks is sometimes a requirement!

Android - see if password to wifi was correct

I plan to make an app that checks if a certain wifi network uses common password. I have a list of most common passwords, but the following function:
public boolean connectTo(String psKey){
WifiConfiguration wc = new WifiConfiguration();
wc.SSID = "\"" + dbw.getSsid() + "\"";
wc.preSharedKey = "\"" + psKey + "\"";
wc.status = WifiConfiguration.Status.ENABLED;
wc = configureCapabs(wc, dbw.getCapability());
int res = wifi.addNetwork(wc);
Toast.makeText(this, "add Network returned " + res , Toast.LENGTH_SHORT).show();
boolean b = wifi.enableNetwork(res, true);
Toast.makeText(this, "enableNetwork returned " + b , Toast.LENGTH_SHORT).show();
if(!b) return false;
boolean fin = /*b ? wifi.reassociate() : */wifi.reconnect();
return fin;
}
it returns true, even if the password was incorrect. Is there a way of checking if the password that I tried to log in with was accepted or rejected?
You are using the reconnect() / reassociate() methods of WiFiManager to check weather or not the connection succeeded but the boolean value that they return means something else. The returned value only tells you about the result of STARTING a given operation. This is because associating and connecting to a WiFi network takes time. These methods however will return instantly and will not block. The actual task of connecting or associating with the network is done asynchronously in the background.
You can monitor the what is happening with the WiFi connection by listening to a specific system wide broadcast:
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
<action android:name="android.net.wifi.STATE_CHANGE"/>
You can find more info about this HERE - Check Torsten's answer.
As far as checking if the password was accepted or rejected by way of a return value, I don't actually know however I can think of two 'possible' alternate strategies that might net the same result?
Not 100% sure as I haven't done it before, however I wonder if Pinging the supplicant would work?
http://developer.android.com/reference/android/net/wifi/WifiManager.html#pingSupplicant()
Either that or checking if you have an IP address now?
http://developer.android.com/reference/android/net/wifi/WifiInfo.html

Android wifimanager always returns true

This is killing me and any help would be greatly appreciated.
I want to connect to an open network using wifi manager. The problem I am having is that the code claims connection to any network - even non-existing ones. Below is the entire code that gets executed and gets called with the SSID of a network. It does not matter what string you pass to it as the SSID of a network, even if no such network exists in any shape or form, the enableNetwork claims returns true, which I believe means it connected to the network.
What I need to do is to make sure I have a connection. So if I pass a network SSID that does not exist (for example, it is out of range) the API should return a failure when attempting to connect.
Any ideas/hints/suggestions would be greatly appreciated.
public boolean conto (String network){
WifiConfiguration wifiConfiguration = new WifiConfiguration();
wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE);
List<WifiConfiguration> configs = null;
int inetId = -1;
// make sure there are no funny stuff in the config
configs = wifi.getConfiguredNetworks();
for (WifiConfiguration config : configs) {
wifi.removeNetwork(config.networkId);
Log.d("********", "Removed Network: SSID=[" + config.SSID + "] and ID=[" + config.networkId + "]");
}
// Now add the network
wifiConfiguration.SSID = "\"" + network + "\"";
wifiConfiguration.hiddenSSID = false;
//wifiConfiguration.priority = 1;
//wifiConfiguration.networkId = 999;
inetId = wifi.addNetwork(wifiConfiguration);
if(inetId < 0) {
Log.d("********", "Could Not Add Network......... [" + wifiConfiguration.SSID + "]");
}
else {
Log.d("********", "Added Network......... [" + wifiConfiguration.SSID + "]");
// Lets be paranoid and double check the config file
Log.d("********", " +++++++++++++++++++++++++ This is what I have in Config File");
configs = wifi.getConfiguredNetworks();
for (WifiConfiguration config : configs) {
Log.d("********", "In the Config file after add, SSID=[" + config.SSID + "], ID=[" + config.networkId + "]");
}
// Now Enable the network
boolean successConnected = wifi.enableNetwork(inetId, true);
//boolean successAssociated = wifi.reassociate(); This did not change the results
if(successConnected) {
Log.d("********", "Connected to......... [" + inetId + "]");
}
else {
Log.d("********", "Could Not Connect to......... [" + inetId + "]");
}
}
return false;
}
I think the problem is that your code is based on the assumption that connecting to a WiFi network is a synchronous process, which it is not. The method enableNetwork() does not block while the connection occurs and then return the resulting success or failure of the connection, it returns immediately and the result is simply whether the request properly reached the WiFi service and the supplicant.
If you want visibility into what is going on with the connection status of the device, you need to monitor the WifiManager. SUPPLICANT_STATE_CHANGED_ACTION and WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION broadcast Intent actions with a BroadcastReceiver. Once you initiate the connection process, these callbacks will tell you how things progress. For example, if you pass in a network that doesn't exist, the connection status should almost immediately go straight to DISCONNECTED. While a valid network will go through the process of associating, authenticating, and connecting. All these steps are enumerated through these broadcasts.

Categories

Resources