So i have this code below:
public static String getBSSID(Context context){
WifiManager wifiManager;
List<ScanResult> results;
List<String> ids = new ArrayList<>();
wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
String essidPrefix = MainFragment.configuration.getEssidPrefix();
String bssid = null;
results = wifiManager.getScanResults();
for(ScanResult scanResult : results){
String scanWifi = scanResult.SSID;
if(scanWifi.startsWith(essidPrefix)){
ids.add(scanResult.BSSID);
bssid = TextUtils.join(",",ids);
}
}
return bssid;
When i detect a certain Wifi BSSID, it will return its BSSID, when it doesnt detect a certain Wifi BSSID, it returns null, what i want to do is when i detect a change from null to not null in BSSID , do something, how should i construct this?
EDITS
public static String getBSSID(Context context){
final Configuration configuration = new Configuration();
final String essidPrefix = MainFragment.configuration.getEssidPrefix();
final WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
if(wifiManager != null){
wifiManager.startScan();
}
final IntentFilter filter = new IntentFilter();
filter.addAction(wifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
final List<String> ids = new ArrayList<>();
final BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
ids.clear();
results = wifiManager.getScanResults();
for(ScanResult scanResult : results){
String scanSSID = scanResult.SSID;
if(scanSSID.startsWith(essidPrefix)){
ids.add(scanResult.BSSID);
configuration.setScanBssid(TextUtils.join(",",ids));
}
}
}
};
if(wifiManager != null){
context.registerReceiver(receiver,filter);
}
return configuration.getScanBssid();
}
First, you have to bear in mind that in order to detect something changed you will have to keep a record of how it was before, to compare.
You can store a previous result from your method and compare. It would help you if you directly returned the list. Also, consider that a Set would help you quickly find what has been added new or removed.
Finally, if you're planning to stick to returning the comma separated String, use TextUtils.join outside of the for loop; you'll see the result is the same, however, more efficient.
I think your approach is wrong. You should use BroadcastReceiver in order to handle updated list of the WiFi access points. Once you call wifiManager.getScanResults(), then you cannot be sure if it's null or not. You should update list of the acces points, once you get it from the device. Check out the code snippet below.
final WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
if(wifiManager != null) {
wifiManager.startScan();
}
final IntentFilter filter = new IntentFilter();
filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
final List<ScanResult> accessPoints = new ArrayList<>();
BroadcastReceiver receiver = new BroadcastReceiver() {
#Override public void onReceive(Context context, Intent intent) {
// you can handle list of the access points here...
accessPoints.clear();
accessPoints.addAll(wifiManager.getScanResults());
// optionally you can call wifiManager.startScan()
// to continuosly receive updated list of the access points
}
}
if(wifiManager != null) {
context.registerReceiver(receiver, filter);
} else {
// optionally log error here...
}
I haven't tested if it code works, but I think it should. At least you have general idea of the approach here. You can adjust it to your needs by playing around with the List depending on the result you want to achieve (e.g. add new APs only, collect everything what your received and then filter it, etc.).
You can also check my library, which helps to solve this problem:
https://github.com/pwittchen/ReactiveWiFi/
Related
I have a "share" button on my Android application which calls Intent.ACTION_SEND to display the available applications that the user can utilize to share a link. But I'd like for the applications to be displayed in a certain order, let's say, Whatsapp, then Messenger, then Facebook, and then all the other ones indiscriminately.
I've tried a few methods, it kind of works when I filter the apps to just those three, but then if I add the rest of them, either the apps always get displayed in the same order (happens on my Xiaomi), or the whole thing gets messed up and even duplicates some apps (happens on the Motorola I tested on).
This is kind of what I tried to do, just for testing purposes:
Intent sharingIntent = new Intent(Intent.ACTION_SEND);
List<ResolveInfo> resInfo = mActivity.getPackageManager().queryIntentActivities(sharingIntent, 0);
I then performed three for() loops that would each fully iterate through the resInfo list. Each loop would search for a specific app (Whatsapp, then Messenger, then Facebook) and add it to the Chooser. I then added another similar for() method, this time in order to add the remaining apps:
for (ResolveInfo resolveInfo : resInfo) {
String packageName = resolveInfo.activityInfo.packageName;
Intent targetedShareIntent = new Intent(android.content.Intent.ACTION_SEND);
targetedShareIntent.setType("text/plain");
targetedShareIntent.putExtra(android.content.Intent.EXTRA_TEXT, shareBody);
targetedShareIntent.setPackage(packageName);
if (!packageName.equals("com.whatsapp") ||
!packageName.equals("com.facebook.orca") ||
!packageName.equals("com.facebook.katana")) {
Toast.makeText(mActivity, packageName, Toast.LENGTH_SHORT).show();
targetedShareIntents.add(targetedShareIntent);
}
}
If I don't add this last method, the Chooser will only display the three first apps I added, and they'll even be displayed in different order if I change the order of the for() loops. But when I do add this method, every app will be displayed in the same order as if I had just regularly called the chooser intent.
Is there any way to work around this?
Note: This answer is for idea only. It is not a complete solution.
I have created the custom http intent which excludes certain apps from the chooser. This might be helpful for you. If I got enough time I will create code for your requirement. I am sharing my code which might be helpful for you.
public static void showBrowserIntent(Activity activity, String fileUrl, String[] forbiddenApps) {
String[] blacklist = new String[]{"com.google.android.apps.docs"};
if (forbiddenApps != null) {
blacklist = forbiddenApps;
}
Intent httpIntent = new Intent(Intent.ACTION_VIEW);
httpIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
httpIntent.setData(Uri.parse(fileUrl));
httpIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
List<Intent> targetedShareIntents = new ArrayList<Intent>();
List<HashMap<String, String>> intentMetaInfo = new ArrayList<HashMap<String, String>>();
Intent chooserIntent;
List<ResolveInfo> resInfo = activity.getPackageManager().queryIntentActivities(httpIntent, 0);
Intent chooser = Intent.createChooser(httpIntent, "Choose Downloader/Browser");
if (!resInfo.isEmpty()) {
for (ResolveInfo resolveInfo : resInfo) {
if (resolveInfo.activityInfo == null
|| Arrays.asList(blacklist).contains(
resolveInfo.activityInfo.packageName))
continue;
//Get all the posible sharers
HashMap<String, String> info = new HashMap<String, String>();
info.put("packageName", resolveInfo.activityInfo.packageName);
info.put("className", resolveInfo.activityInfo.name);
String appName = String.valueOf(resolveInfo.activityInfo
.loadLabel(activity.getPackageManager()));
info.put("simpleName", appName);
//Add only what we want
if (!Arrays.asList(blacklist).contains(
appName.toLowerCase())) {
intentMetaInfo.add(info);
}
}
if (!intentMetaInfo.isEmpty()) {
// sorting for nice readability
Collections.sort(intentMetaInfo,
new Comparator<HashMap<String, String>>() {
#Override
public int compare(
HashMap<String, String> map,
HashMap<String, String> map2) {
return map.get("simpleName").compareTo(
map2.get("simpleName"));
}
});
// create the custom intent list
for (HashMap<String, String> metaInfo : intentMetaInfo) {
Intent targetedShareIntent = (Intent) httpIntent.clone();
targetedShareIntent.setPackage(metaInfo.get("packageName"));
targetedShareIntent.setClassName(
metaInfo.get("packageName"),
metaInfo.get("className"));
targetedShareIntents.add(targetedShareIntent);
}
String shareVia = "Open with";
String shareTitle = shareVia.substring(0, 1).toUpperCase()
+ shareVia.substring(1);
chooserIntent = Intent.createChooser(targetedShareIntents
.remove(targetedShareIntents.size() - 1), shareTitle);
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS,
targetedShareIntents.toArray(new Parcelable[]{}));
activity.startActivity(chooserIntent);
}
} else {
activity.startActivity(chooser);
}
}
I am having an issue when try to connect to wireless network.
My code works ok I sucessfully connect to the network whit the problems described below.
I have a listview whit the wifi scan results.
When I click the first time my receiver is not getting the "completed" state.
After clicking the second time , and , without chosing any network it get connected and my code inside the "complete" is executed.
The code below is called from another class that thre reason why it is static
Coonect Code:
public static boolean connect(String ssid,String password)
{
String networkSSID = ssid;
String networkPass = password;
WifiConfiguration conf = new WifiConfiguration();
conf.SSID = "\"" + networkSSID + "\"";
conf.preSharedKey = "\""+ networkPass +"\"";
//WifiManager wifiManager = (WifiManager)context.getSystemService(Context.WIFI_SERVICE);
int netid=mainWifi.addNetwork(conf);
mainWifi.disconnect();
mainWifi.enableNetwork(netid, true);
//mainWifi.reconnect(); <-- exact the same issue discommenting this line
return true;
}
On the class were the connect is been called I have registered BradcastReceiver as follow:
public void onClick(View v)
{
mainWifi = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
wifiinfo = mainWifi.getConnectionInfo();
AuthWifi authWifi = new AuthWifi();
IntentFilter mIntentFilter = new IntentFilter();
mIntentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
getApplicationContext().registerReceiver(authWifi, mIntentFilter);
ClientManager.scan(CameraActivity.this, mainWifi);
}
my broadcast receiver
public class AuthWifi extends BroadcastReceiver {
#Override
public void onReceive(Context c, Intent intent) {
String action = intent.getAction();
if (action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION)) {
SupplicantState supl_state = ((SupplicantState) intent.getParcelableExtra(WifiManager.EXTRA_NEW_STATE));
switch (supl_state) {
case COMPLETED:
/////////////IF I AM CONNECTED THE WIFI SSID I CHOSE FROM A LISTVIEW THEN ---->
if(wifiinfo != null & wifiinfo.getSSID()!=null & ClientManager.getSSID() !=null& !conectado ) {
if (wifiinfo.getSSID().contains(ClientManager.getSSID().trim())) ///I know here is better .equals() /// I have contain for my own reasons
conectado = true;
/*HERE I DO SOME THINGS WHEN CONNECTED (I CALL A RUNNABLE TO MAKE A SERVER SOCKET)*/
}
}
break;
case DISCONNECTED:
Log.i("SupplicantState", "Disconnected");
conectado = false;
if (ClientStartReceive.isStopped)
{
ClientStartReceive.stop();
}
break;
default:
Log.i("SupplicantState", "Unknown");
break;
}
int supl_error = intent.getIntExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, -1);
if (supl_error == WifiManager.ERROR_AUTHENTICATING) {
/////HERE I MANAGE AUTHENTICATION ERROR
}
}
}
}
Hope someone is able to help :( if you need more code to troubleshoot please let me know.
If you have some reference to help me even if i need to rebuild the code is accepted also.. My goal is be able to connect to a network ,show for authentication errors and execute some code on connection susscess.
Sorry for my english I think you have gessed I am not native.
Regards
As doc indicate that “COMPLETED“ use as follow:
This state indicates that the supplicant has completed its processing for the association phase and that data connection is fully configured. Note, however, that there may not be any IP address associated with the connection yet.
You should not rely this state to ensure your connection is completed. Instead, you can register an BroadcastReceiver listener for network status change.
I tried this test but getHits() always return 0. Anyone can help me?
public void testSettingsAboutShazamClickOnLink() {
Instrumentation inst = getInstrumentation();
IntentFilter intentFilter = new IntentFilter(android.content.Intent.ACTION_SENDTO);
intentFilter.addDataScheme("mailto");
ActivityMonitor monitor = inst.addMonitor(intentFilter, null, false);
final Intent emailIntent = new Intent(android.content.Intent.ACTION_SENDTO);
emailIntent.setData(Uri.parse("mailto:"));
emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
assertEquals(0, monitor.getHits());
inst.getContext().startActivity(emailIntent);
monitor.waitForActivityWithTimeout(5000);
assertEquals(1, monitor.getHits());
inst.removeMonitor(monitor);
}
I solved this problem. I used Solo of Robotium and apparently Solo inserts an ActivityMonitor that matches every Filter, so when you call startActivity, it calls execStartActivity in the Instrumentation class, whose code is
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
for (int i=0; i<N; i++) {
final ActivityMonitor am = mActivityMonitors.get(i);
if (am.match(who, null, intent)) {
am.mHits++;
if (am.isBlocking()) {
return requestCode >= 0 ? am.getResult() : null;
}
break;
}
}
}
}
As soon as there is an ActivityMonitor that matches something the mHits of that monitor is increased end the "cycle for" breaks. Given that calling Solo was the first thing I did, the first ActivityMonitor to check is the one of Solo, given that this matches everything, any other ActivityMonitors added after it is not checked, so no mHits variable is increased for the other monitors. If you want to understand more see the execStartMonitor method of Instrumentation class, remembering that this method is called after startActivity is called.
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/1.5_r4/android/app/Instrumentation.java#Instrumentation.execStartActivity%28android.content.Context%2Candroid.os.IBinder%2Candroid.os.IBinder%2Candroid.app.Activity%2Candroid.content.Intent%2Cint%29
I know this question has been asked multiple times, but nobody has been able to come up with a working answer from what I have seen.
Im working on an app to intercept text messages and depending on the sending #, pop up with a custom alert. I have it working beautifully with a broadcast receiver, however if the user has goSms installed the onReceive() method is never called as goSms aborts it before it ever reaches my app.
To get around this, Im trying a content observer on content://sms/
Its working just fine, however the onChange() is called twice, with exactly the same parameters. Ive tried to check the time stamps, but they are the same, as is the type and every other parameter I have set.
From what I've seen, this is a common issue, but not one that I've seen answered anywhere.
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
querySMS();
}
protected void querySMS() {
Cursor cur = getContentResolver().query(u, null, null, null, null);
cur.moveToNext(); // this will make it point to the first record, which is the last SMS sent
String type = cur.getString(cur.getColumnIndex("type"));
String body = cur.getString(cur.getColumnIndex("body")); //content of sms
String add = cur.getString(cur.getColumnIndex("address")); //phone num
if (type.equals("1")) {
if (add.equals(Test.SENDER)) {
String[] bodys = body.split(" ", 7);
if (bodys[0].equals("test")) {
test = true;
}
cat = bodys[1];
level = bodys[2];
urgency = bodys[3];
certainty = bodys[4];
carrier = bodys[5];
message = bodys[6];
final Intent intent = new Intent(context, AlertActivity.class);
Bundle b = new Bundle();
b.putString("title", cat);
b.putString("certainty", certainty);
b.putString("urgency", urgency);
b.putString("level", level);
b.putString("message", message);
b.putBoolean("test", test);
intent.putExtras(b);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
TelephonyManager manager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
carrierName = manager.getNetworkOperatorName();
if (carrierName.replaceAll(" ", "").equals(carrier)) {
context.startActivity(intent);
} else {
//testing
Toast.makeText(context, carrierName.replaceAll(" ", ""), Toast.LENGTH_LONG).show();
}
}
}
}
Because of the onChange() being fired twice, Im getting two alerts as well. I cannot for the life of me figure out a way around this.
If the two are identical:
store each message recv'd
compare it to previous messages recv'd
if not found, process
if found, discard the message
The life of the messages stored should be infinitesimal, a little circular buffer of 5 messages should be fine.
here is my code, it works fine for me
public class SmsObserver extends ContentObserver {
private Context context;
private static int initialPos;
private static final String TAG = "SMSContentObserver";
private static final Uri uriSMS = Uri.parse("content://sms/sent");
public SmsObserver(Handler handler, Context ctx) {
super(handler);
context = ctx;
initialPos = getLastMsgId();
}
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
queryLastSentSMS();
}
public int getLastMsgId() {
Cursor cur = context.getContentResolver().query(uriSMS, null, null, null, null);
cur.moveToFirst();
int lastMsgId = cur.getInt(cur.getColumnIndex("_id"));
Log.i(TAG, "Last sent message id: " + String.valueOf(lastMsgId));
return lastMsgId;
}
protected void queryLastSentSMS() {
new Thread(new Runnable() {
#Override
public void run() {
Cursor cur =
context.getContentResolver().query(uriSMS, null, null, null, null);
if (cur.moveToNext()) {
try {
if (initialPos != getLastMsgId()) {
// Here you get the last sms. Do what you want.
String receiver = cur.getString(cur.getColumnIndex("address"));
System.out.println(" Receiver Ph no :"+receiver);
// Then, set initialPos to the current position.
initialPos = getLastMsgId();
}
} catch (Exception e) {
// Treat exception here
}
}
cur.close();
}
}).start();
}
}//End of class SmsObserver
You can save last message's id and compare it to the id of the message that is returned by cur in onChange. you then can simply disregard the message if ids are the same.
// might contain mistakes, but you'll get the idea:
protected void querySMS() {
Cursor cur = getContentResolver().query(u, null, null, null, null);
cur.moveToNext();
if (lastId == cur.getLong(cur.getColumnIndex("_id")))
return;
lastId = cur.getLong(cur.getColumnIndex("_id"));
... //continue as it was
}
However - GO SMS only prevents other app's from recieving Broadcast if the user selected this option (Recieve Settings - Disable other message notification) - so if the user does not want other apps to disturb him - I think it's good idea not to do so.
I just use SharedPreference to remark last SMS info (like: id\type ...). if it is the same, I will return.
I'm creating notifications in my Android application, and would like to have an option in my preferences to set what sound is used for the notification. I know that in the Settings application you can choose a default notification sound from a list. Where does that list come from, and is there a way for me to display the same list in my application?
Just copy/pasting some code from one of my apps that does what you are looking for.
This is in an onClick handler of a button labeled "set ringtone" or something similar:
Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, "Select Tone");
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, (Uri) null);
this.startActivityForResult(intent, 5);
And this code captures the choice made by the user:
#Override
protected void onActivityResult(final int requestCode, final int resultCode, final Intent intent) {
if (resultCode == Activity.RESULT_OK && requestCode == 5) {
Uri uri = intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
if (uri != null) {
this.chosenRingtone = uri.toString();
} else {
this.chosenRingtone = null;
}
}
}
Also, I advise my users to install the "Rings Extended" app from the Android Market. Then whenever this dialog is opened on their device, such as from my app or from the phone's settings menu, the user will have the additional choice of picking any of the mp3s stored on their device, not just the built in ringtones.
Or just stick this in your preferences XML:
<RingtonePreference android:showDefault="true"
android:key="Audio" android:title="Alarm Noise"
android:ringtoneType="notification" />
Full content of my sample XML just for context:
<?xml version="1.0" encoding="UTF-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<EditTextPreference android:title="Some value"
android:key="someval"
android:summary="Please provide some value" />
<EditTextPreference android:title="Some other value"
android:key="someval2"
android:summary="Please provide some other value" />
<RingtonePreference android:showDefault="true"
android:key="Audio" android:title="Alarm Noise"
android:ringtoneType="notification" />
</PreferenceScreen>
This is the method I use to get a list of notification sounds available in the phone :)
public Map<String, String> getNotifications() {
RingtoneManager manager = new RingtoneManager(this);
manager.setType(RingtoneManager.TYPE_NOTIFICATION);
Cursor cursor = manager.getCursor();
Map<String, String> list = new HashMap<>();
while (cursor.moveToNext()) {
String notificationTitle = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX);
String notificationUri = cursor.getString(RingtoneManager.URI_COLUMN_INDEX);
list.put(notificationTitle, notificationUri);
}
return list;
}
EDIT: This is for the comment regarding how to set the sound in the NotificationCompat.Builder. This method instead gets the ringtone's ID which is what the phone uses, instead of the human readable TITLE the other method got. Combine the uri and the id, and you have the ringtones location.
public ArrayList<String> getNotificationSounds() {
RingtoneManager manager = new RingtoneManager(this);
manager.setType(RingtoneManager.TYPE_NOTIFICATION);
Cursor cursor = manager.getCursor();
ArrayList<String> list = new ArrayList<>();
while (cursor.moveToNext()) {
String id = cursor.getString(RingtoneManager.ID_COLUMN_INDEX);
String uri = cursor.getString(RingtoneManager.URI_COLUMN_INDEX);
list.add(uri + "/" + id);
}
return list;
}
The above code will return a list of strings like "content://media/internal/audio/media/27".. you can then pass one of these strings as a Uri into the .setSound() like:
.setSound(Uri.parse("content://media/internal/audio/media/27"))
Hope that was clear enough :)
public void listRingtones() {
RingtoneManager manager = new RingtoneManager(this);
manager.setType(RingtoneManager.TYPE_NOTIFICATION);
// manager.setType(RingtoneManager.TYPE_RINGTONE);//For Get System Ringtone
Cursor cursor = manager.getCursor();
while (cursor.moveToNext()) {
String title = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX);
String uri = manager.getRingtoneUri(cursor.getPosition());
String ringtoneName= cursor.getString(cursor.getColumnIndex("title"));
Log.e("All Data", "getNotifications: "+ title+"-=---"+uri+"------"+ringtoneName);
// Do something with the title and the URI of ringtone
}
}
Here's another approach (in Kotlin), build from other answers in this question, that allows you to specify the name of the tone, and then play it:
fun playErrorTone(activity: Activity, context: Context, notificationName: String = "Betelgeuse") {
val notifications = getNotificationSounds(activity)
try {
val tone = notifications.getValue(notificationName)
val errorTone = RingtoneManager.getRingtone(context, Uri.parse(tone))
errorTone.play()
} catch (e: NoSuchElementException) {
try {
// If sound not found, default to first one in list
val errorTone = RingtoneManager.getRingtone(context, Uri.parse(notifications.values.first()))
errorTone.play()
} catch (e: NoSuchElementException) {
Timber.d("NO NOTIFICATION SOUNDS FOUND")
}
}
}
private fun getNotificationSounds(activity: Activity): HashMap<String, String> {
val manager = RingtoneManager(activity)
manager.setType(RingtoneManager.TYPE_NOTIFICATION)
val cursor = manager.cursor
val list = HashMap<String, String>()
while (cursor.moveToNext()) {
val id = cursor.getString(RingtoneManager.ID_COLUMN_INDEX)
val uri = cursor.getString(RingtoneManager.URI_COLUMN_INDEX)
val title = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX)
list.set(title, "$uri/$id")
}
return list
}
It can probably take some refactoring and optimization, but you should get the idea.