I'm building a client server android app that has to do with stores and I want (when the user is close to 2 or 3 stores with their database online) my app to understand that he can connect to one of these stores' databases and list them to the screen. (just what is happenning with the available wifis when we want to connect to the internet)
How can I achieve that?
I hope I made clear what I'm looking for.
Correct me if I'm wrong. You are writing an app, that can access data from a server, where the server/database belongs to a store?
If my understanding of your question is correct, do the following:
Create a connection broadcast that listens for changes in connectivity
Create a GPS Manager that compares the current location to that based off the location from the stores
If in range of one or more stores AND a connection is available, query the server via a restfull request, on the server side, fire a query, see if you get any result(I don't know if there exists a specific query to check for database connectivity from server-side code to database) If you get a result, the database is available, send a result back from the server to the client, and list the database/store as available.
I personally would use JSON for the requests from client <-> server. As it is lightweight and easy to use.
For writing a broadcaster: BraodcastReciever
Example:
private Context _context;
private State _state;
private boolean _listening;
private String _reason;
private boolean _isFailOver;
private NetworkInfo _networkInfo;
private NetworkInfo _otherNetworkInfo;
private ConnectivityBroadcastReceiver _receiver;
/**
* The broadcast that listens to connectivity changes(wifi, mobile network etc)
* */
private class ConnectivityBroadcastReceiver extends BroadcastReceiver {
/**
* Called when connectivity state changes
*
* #param Context the context
* #param Intent the intent containing the information about the change
* */
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(!action.equals(ConnectivityManager.CONNECTIVITY_ACTION) || _listening == false) {
Log.w(TAG, "onReceived() called with " + _state.toString() + " and " + intent);
return;
}
boolean noConnectivity = intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
//Set the state according to current connectivity.
if(noConnectivity) {
_state = State.NOT_CONNECTED;
} else {
_state = State.CONNECTED;
}
//If current state is CONNECTED. Start background services, otherwise stop services.
switch(_state) {
case CONNECTED:
//Do stuff when connected
break;
case NOT_CONNECTED:
//Do stuff if not connected
break;
}
_networkInfo = (NetworkInfo)intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
_otherNetworkInfo = (NetworkInfo)intent.getParcelableExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO);
_reason = intent.getStringExtra(ConnectivityManager.EXTRA_REASON);
_isFailOver = intent.getBooleanExtra(ConnectivityManager.EXTRA_IS_FAILOVER, false);
Log.d(TAG, "onRecieve(): _networkInfo= " + _networkInfo + " _otherNetworkInfo= " + (_otherNetworkInfo == null ? "[none]" : _otherNetworkInfo +
" noConn= " + noConnectivity) + " _state= " + _state.toString());
}
};
I am not posting the entire code. I wrote a wrapper around the ConnectivityBroadcastReceiver. But with the given code you should be able to get far enough.Note that in the code State is an enum containing 3 values: CONNECTED, NOT_CONNECTED, UNKNOWN
As for the GPS manager:
/**
* <h1>GPSManager</h1>
*
* <p>
* Manager for GPS tracking.
* Able to enable and disable GPS tracking for the application.
* </p>
* */
public class GPSManager {
public static final String TAG = "LocationFinder";
private double _lat;
private double _lon;
private float _accuracy;
private Context _context;
private LocationManager _locManager;
private LocationListener _locListener;
private static GPSManager _instance;
/**
* Constructor.
*
* #param context The context of the caller.
* */
private GPSManager(Context context) {
this._context = context;
this._locListener = new LocationTracker();
}
/**
* GPSManager is singleton. Retrieve the shared instance.
*
* #param context The context of the caller.
* #return GPSManager An instance of the GPSManager class.
* */
public static synchronized GPSManager getInstance(Context context) {
if(_instance == null) {
_instance = new GPSManager(context);
}
return _instance;
}
/**
* Start tracking GPS locations.
* */
public void startGpsTracking() {
_locManager = (LocationManager)_context.getSystemService(Context.LOCATION_SERVICE);
_locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
0, 0, _locListener);
}
/**
* Stop tracking GPS locations.
* */
public void stopGpsTracking() {
_locManager.removeUpdates(_locListener);
_locManager = null;
}
/**
* Retrieve the latitude from the GPSManager.
*
* #return double The latitude.
* */
public double getLatitude() {
return _lat;
}
/**
* Retrieve the longitude from the GPSManager.
*
* #return double The longitude.
* */
public double getLongitude() {
return _lon;
}
/**
* Check if the GPSManager has a fix on a location.
*
* #return boolean True if GPSManager has a fix, otherwise false.
* */
public boolean hasFix() {
if(_lat != 0 && _lon != 0)
return true;
else
return false;
}
/**
* Retrieve the accuracy of the fix.
*
* #return float The accuracy.
* */
public float getAccuracy() {
return _accuracy;
}
/**
* Retrieve the last known location.
*
* #return Location The last known location.
* */
public Location getLastLocation() {
return _locManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
}
/**
* <h1>LocationTracker</h1>
*
* <p>Tracks the location for the GPSManager.</p>
* */
private class LocationTracker implements LocationListener {
/** (non-Javadoc)
* #see android.location.LocationListener#onLocationChanged(android.location.Location)
*/
#Override
public void onLocationChanged(Location location) {
_lat = location.getLatitude();
_lon = location.getLongitude();
_accuracy = location.getAccuracy();
}
/** (non-Javadoc)
* #see android.location.LocationListener#onProviderDisabled(java.lang.String)
*/
#Override
public void onProviderDisabled(String provider) {
Log.d(TAG, "Gps Disabled");
}
/** (non-Javadoc)
* #see android.location.LocationListener#onProviderEnabled(java.lang.String)
*/
#Override
public void onProviderEnabled(String provider) {
Log.d(TAG, "Gps Enabled");
}
/** (non-Javadoc)
* #see android.location.LocationListener#onStatusChanged(java.lang.String, int, android.os.Bundle)
*/
#Override
public void onStatusChanged(String provider, int status, Bundle extras) {};
};
}
Expand this with your own needs. Most code in the gps manager is self explanatory.
And for the restfull stuff, theres so many different approaches, you will have to look that up yourself.
As answer to your comment, I will show you my implementation of the webrequest.
I personally use apache-mime4j-0.6.jar AND httpmime-4.0.1.jar.
WebService _service = new WebService();
#Override
protected String doInBackground(String... arg0) {
try {
MultipartEntity multipart = new MultipartEntity();
multipart.addPart("username", new StringBody(_inputName));
multipart.addPart("password", new StringBody(_inputPass));
_service.post(QfConfig.RESTFUL_LOGIN_URL, multipart);
long response = _service.getLongResponse();
if(response != 0) {
_pgUserId = response;
_onlineValidated = true;
}
} catch(Exception e) {
e.printStackTrace();
}
return null;
}
I don't know wether you want to POST or GET. The above example uses POST.
I create a MultipartEntity, and add 2 parts to it. The multipart will be send across to the server as a form where the name of the POST value is username, and the VALUE of the post where the name is username is new StringBody(_inputName).
For the post section in my WebService class:
public void post(String url, MultipartEntity postData) {
HttpClient client = null;
HttpPost post = null;
HttpResponse httpResponse = null;
HttpEntity entity = null;
InputStream _inStream = null;
try {
client = new DefaultHttpClient();
post = new HttpPost();
URI uri = URI.create(url);
post.setURI(uri);
post.setEntity(postData);
//Execute the HttpPost request and store the response in httpResponse.
httpResponse = client.execute(post);
//Set the response code from the request's responst.
setResponseCode(httpResponse.getStatusLine().getStatusCode());
//Retrieve the entity from the response.
entity = httpResponse.getEntity();
if(entity != null) {
//Retrieve the content from the entity.
_inStream = entity.getContent();
//Convert the InputStream to String and set the String response to the returned value.
setStringResponse(IOUtility.convertStreamToString(_inStream));
//Close the InputStream.
Log.d(TAG, getStringResponse());
}
//try to create a numeric value of the response result and store it if so
if(GeneralUtil.isNumeric(getStringResponse())) {
setLongResponse(Long.parseLong(getStringResponse()));
}
Log.d(TAG, httpResponse.getStatusLine().getReasonPhrase());
} catch(Exception e) {
e.printStackTrace();
setResponseCode(0);
setLongResponse(0);
setStringResponse("");
} finally {
try {
_inStream.close();
} catch (Exception ignore) {}
}
}
I work via https, which is more complex so I left those things out. What I do here is, create a new httpClient and HttpPost, set the URI if the post, add the multipart data to post.setEntity() and then execute the request and save the response in a HttpResponse object.
I then retrieve the entity and save it as HttpEntity, as to where I get the response content out of. Which can be a JSON string, a number, whatever you want basically.
I then set some methods which helps me retrieve the results easily by getters and setters.
For a HttpGet its even easier, you only need to pass an url , instead of a HttpPost object you create an HttpGet object, pass it the url, _client.execute([HttpGet object]) and retrieve the result the same way.
In the php script, you can literally just use $_POST['username'] which would give the value of username that u set in StringBody in the above code.
With a get, I would advise sending an url(with or without params, and then send back a JSON string as result to the get request.
Let me know if you need more help. I think this is as far as I can go though. I can't show the php side cause I use a self designed framework.
Related
Below is code which I am using since 2015 but suddenly it is not working for Android 7.0 (working for all other below 7.0 OS phone except xiomi phone), I checked periodic sync "period" using below command:
run in power shell
adb shell dumpsys content | Select-String -pattern "package.name";
Below is the result in power shell which tells me app is taking 86400000 millis (24 hours) as periodic sync time.
JobId: 109182, WorkIndia u0, in.workindia.workindiaandroid.app, PERIODIC,
reason: Periodic, period:
**86400000, flexMillis: 3456000**
in.workindia.workindiaandroid.app 1 true 0 0 0
1 0 1 00:06 SERVER SUCCESS
in.workindia.workindiaandroid.app : 1/100% 6s/100%
#1 : 2017-11-23 20:50:52 SERVER 6.5s
WorkIndia/app.workindia.in u0
in.workindia.workindiaandroid.app AutoSync
ServiceInfo: SyncAdapterType {name=in.workindia.workindiaandroid.app,
type=app.workindia.in, userVisible=true,
supportsUploading=false, isAlwaysSyncable=true, allowParallelSyncs=false,
settingsActivity=null,
packageName=in.workindia.nileshdungarwal.workindiaandroid.debug},
ComponentInfo{in.workindia.nileshdungarwal.workindiaandroid.debug/
in.workindia.nileshdungarwal.sync.SyncService}, uid
10085
Here is my class code:
public class SyncAdapter extends AbstractThreadedSyncAdapter {
public static final String TAG = SyncAdapter.class.getSimpleName();
// Interval at which to sync with the weather, in seconds.
// 60 seconds (1 minute) * 60(1 hour) * 5 = 5 hours
public static final int SYNC_INTERVAL = AppConstants.SYNC_INTERVAL_GLOBAL;
public static final int SYNC_FLEXTIME = SYNC_INTERVAL / 3;
public SyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
}
#Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
/*Try to Sync Also */
RetrofitSyncAll.syncApplication();
}
/**
* Helper method to have the sync adapter sync immediately
*
* #param context The context used to access the account service
*/
public static void syncImmediately(Context context) {
Log.d(TAG, "syncImmediately Called.");
EmployeeProfile profile = ObjectGraph.getEmployeeProfile();
if (profile != null && profile.getFullName() != null) {
Bundle bundle = new Bundle();
bundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
ContentResolver.requestSync(getSyncAccount(context), context.getString(R.string.content_authority), bundle);
}
}
/**
* Helper method to get the fake account to be used with SyncAdapter, or make a new one
* if the fake account doesn't exist yet. If we make a new account, we call the
* onAccountCreated method so we can initialize things.
*
* #param context The context used to access the account service
* #return a fake account.
*/
public static Account getSyncAccount(Context context) {
// Get an instance of the Android account manager
AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
// Create the account type and default account
Account newAccount = new Account(context.getString(R.string.app_name), context.getString(R.string.sync_account_type));
String password = null;
try {
password = accountManager.getPassword(newAccount);
} catch (Exception e) {
Log.e(TAG, e.getMessage(), e);
Crashlytics.logException(e);
}
// If the password doesn't exist, the account doesn't exist
if (null == password) {
/*
* Add the account and account type, no password or user data
* If successful, return the Account object, otherwise report an error.
*/
if (!accountManager.addAccountExplicitly(newAccount, "", null)) {
return null;
}
/*
* If you don't set android:syncable="true" in
* in your <provider> element in the manifest,
* then call ContentResolver.setIsSyncable(account, AUTHORITY, 1)
* here.
*/
onAccountCreated(newAccount, context);
if (AppConstants.DEBUG) {
Toast.makeText(context, "getSyncAccount Account Creation ", Toast.LENGTH_SHORT).show();
}
}
return newAccount;
}
private static void onAccountCreated(Account newAccount, Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
ContentResolver.setSyncAutomatically(newAccount, context.getString(R.string.content_authority), true);
SyncAdapter.configurePeriodicSync(context, SYNC_INTERVAL, SYNC_FLEXTIME);
} else {
SyncAdapter.configurePeriodicSync(context, SYNC_INTERVAL, SYNC_FLEXTIME);
ContentResolver.setSyncAutomatically(newAccount, context.getString(R.string.content_authority), true);
}
/*
* Finally, let's do a sync to get things started
*/
syncImmediately(context);
}
/**
* Helper method to schedule the sync adapter periodic execution
*/
private static void configurePeriodicSync(Context context, int syncInterval, int flexTime) {
Account account = getSyncAccount(context);
String authority = context.getString(R.string.content_authority);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// we can enable inexact timers in our periodic sync
SyncRequest request = new SyncRequest.Builder().
syncPeriodic(syncInterval, flexTime).
setSyncAdapter(account, authority).
setExtras(new Bundle()).build();
ContentResolver.requestSync(request);
} else {
ContentResolver.addPeriodicSync(account,
authority, new Bundle(), syncInterval);
}
}
public static void initializeSyncAdapter(Context context) {
getSyncAccount(context);
if (AppConstants.DEBUG) {
Toast.makeText(context, "initializeSyncAdapter Perform Sync", Toast.LENGTH_SHORT).show();
}
}
}
I use an observer pattern to monitor when there is a change in network connectivity. The issue I am experiencing is that if power saving mode is enabled on any device, returning from the background to the foreground of my application will trigger "no network" momentarily when checking for connectivity. Because of the nature of observer pattern, this change will bring up my no connection dialog, even though milliseconds later a connection is restored.
The connection signal remains strong when returning to the app from the background, but for some reason power saving mode tricks the system into thinking there is no connection when there is. How do I control this? Is there some sort of way to ignore checking for connectivity if power saving mode is active?
Here is my network observer class. I register receivers throughout my app and utilize the app's Activity lifecycle to determine when I should be listening for changes in network and when I should not. Thanks in advance!
public class NetworkReceiver extends BroadcastReceiver {
private static final String TAG = NetworkReceiver.class.getSimpleName();
private static final List<NetworkStatusObserver> mObserverList = new ArrayList<>();
private static boolean isNetworkConnected = true;
#Override
public void onReceive(Context context, Intent intent) {
Logger.i(TAG, "onReceive() broadcast");
boolean disconnected = intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
boolean isNetworkConnectedCurrent;
if (disconnected) {
isNetworkConnectedCurrent = false;
} else {
NetworkInfo networkInfo;
if (Build.VERSION.SDK_INT < 17) {
networkInfo = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
} else {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
networkInfo = cm.getActiveNetworkInfo();
}
isNetworkConnectedCurrent = !FrameworkUtils.checkIfNull(networkInfo) && networkInfo.isConnectedOrConnecting();
}
if (isNetworkConnectedCurrent != isNetworkConnected) {
isNetworkConnected = isNetworkConnectedCurrent;
Logger.d(TAG, "NetworkStatus.onReceive - isNetworkConnected: " + isNetworkConnected);
notifyObservers(isNetworkConnected);
}
}
/**
* Lets all {#link NetworkStatusObserver}s know if the DEVICE is connected to a network.
*
* #param isNetworkConnectedCurrent
*/
private void notifyObservers(Boolean isNetworkConnectedCurrent) {
for (NetworkStatusObserver networkStatusObserver : mObserverList) {
networkStatusObserver.notifyConnectionChange(isNetworkConnectedCurrent);
}
}
/**
* Add observer to observer list
*
* #param observer
*/
public void addObserver(NetworkStatusObserver observer) {
mObserverList.add(observer);
}
/**
* Remove observer from observer list
*
* #param observer
*/
public void removeObserver(NetworkStatusObserver observer) {
mObserverList.remove(observer);
}
/**
* Retrieve observer list size
*
* #return
*/
public int getObserverSize() {
return mObserverList.size();
}
/**
* Check if receiver is added to observer list
*
* #param observer
* #return
*/
public boolean contains(NetworkStatusObserver observer) {
return mObserverList.contains(observer);
}
/**
* Method is used to print observer list
*/
public void printObserverList() {
Logger.i(TAG, "===== PRINT OBSERVER LIST ===== ");
for (int i = 0; i < mObserverList.size(); i++) {
Logger.i(TAG, String.format("item(%d): %s", i, mObserverList.get(i).toString()));
}
}
/**
* Interface for monitoring network status change
*/
public interface NetworkStatusObserver {
void notifyConnectionChange(boolean isConnected);
}
}
I'm new in developing with ACR or NFC Reader, especially for Android. And recently I need to use an ACR35 and I've got the SDK and the example from this acs official website. And It works just fine as an example.
And now I need to create an activity that will always be ready to check whether nfc card is tapped. But the problem is I don't know how to detect when the nfc card is tapped and I don't know what to do next, and I can't find the way out from the example as it detects nfc card when I touch the 'transmit' button, it doesn't do it automatically.
Please give me with example code.
Thanks for your answer.
A bit late, but it might help someone.
I have found following project on the git hub. It shows how to read tag id from the acr35
here
.
I have been using acr35 for some time to read the tag id. From my experience is a buggy device on Android. I tested around 10 devices and it worked only on 3...
I read from it every second. It returns results from the last card even though it is not present there anymore. Therefore; I have to reset it twice per every successful read and it takes around 6 seconds to get the device in reading state again... Also be very careful with multithreading.
My implementation based on mentioned project - added simple locking to stop querying the card after successfull reading + full device reset, filternig same uuid which was read in very short time after it was read previously:
ACR3x class
import com.acs.audiojack.AudioJackReader;
import android.media.AudioManager;
import sk.tido.util.ByteHex;
import java.util.Date;
/**
* This class allows control of the ACR35 reader sleep state and PICC commands
*/
public class Acr3x {
private Acr3xTransmitter transmitter;
private AudioManager mAudioManager;
private AudioJackReader mReader;
private boolean firstReset = true; /** Is this the first reset of the reader? */
/** APDU command for reading a card's UID */
private final byte[] apdu = { (byte) 0xFF, (byte) 0xCA, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
/** Timeout for APDU response (in <b>seconds</b>) */
private final int timeout = 1;
private int acr3xCardType = AudioJackReader.PICC_CARD_TYPE_ISO14443_TYPE_A
| AudioJackReader.PICC_CARD_TYPE_ISO14443_TYPE_B
| AudioJackReader.PICC_CARD_TYPE_FELICA_212KBPS
| AudioJackReader.PICC_CARD_TYPE_FELICA_424KBPS
| AudioJackReader.PICC_CARD_TYPE_AUTO_RATS;
private int acr3xStartAudioLevel = 0;
private Object locking = new Object();
private String lastUuid = "";
private Date lastUuidDate = new Date();
public Acr3x(AudioManager mAudioManager){
this.mAudioManager = mAudioManager;
}
public void start(final Acr3xNotifListener listener){
Runnable r = new Runnable(){
#Override
public void run() {
if(mReader == null){
mReader = new AudioJackReader(mAudioManager);
}
System.out.println("ACR35 reader start");
acr3xStartAudioLevel = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
System.out.println("acr3x start audio stream level: " + acr3xStartAudioLevel);
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,
mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC), 0);
System.out.println("acr3x set audio stream level: " + mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
mReader.start();
mReader.setSleepTimeout(30);
mReader.setOnFirmwareVersionAvailableListener(new AudioJackReader.OnFirmwareVersionAvailableListener() {
#Override
public void onFirmwareVersionAvailable(AudioJackReader reader,
String firmwareVersion) {
System.out.println("acr3x firmware version: " + firmwareVersion);
if(listener != null){
listener.onFirmwareVersionAvailable(firmwareVersion);
}
Acr3x.this.read(listener);
}
});
mReader.reset(new AudioJackReader.OnResetCompleteListener(){
#Override
public void onResetComplete(AudioJackReader arg0) {
mReader.getFirmwareVersion();
}
});
}
};
Thread t = new Thread(r, "Acr3xInitThread");
t.start();
}
/**
* Sets the ACR35 reader to continuously poll for the presence of a card. If a card is found,
* the UID will be returned to the Apache Cordova application
*
* #param callbackContext: the callback context provided by Cordova
* #param cardType: the integer representing card type
*/
public void read(final Acr3xNotifListener callbackContext){
System.out.println("acr3x setting up for reading...");
firstReset = true;
/* Set the PICC response APDU callback */
mReader.setOnPiccResponseApduAvailableListener
(new AudioJackReader.OnPiccResponseApduAvailableListener() {
#Override
public void onPiccResponseApduAvailable(AudioJackReader reader,
byte[] responseApdu) {
/* Update the connection status of the transmitter */
transmitter.updateStatus(true);
/* Print out the UID */
String uuid = ByteHex.bytesToHex(responseApdu);
if(uuid.equalsIgnoreCase("0x9000")){
return;
}
if(uuid.endsWith("9000")){
uuid = uuid.substring(0, uuid.length() - 4);
}
if(uuid.equals(lastUuid)){ // na odfiltrovanie opakujucich sa uuid z citacky z predchadzajuceho citania
if(new Date().getTime() - lastUuidDate.getTime() < 3000){
return;
}
}
lastUuid = uuid;
lastUuidDate = new Date();
synchronized(locking){
System.out.println("acr3x uuid: " + uuid);
if(callbackContext != null){
callbackContext.onUUIDAavailable(uuid);
}
System.out.println("acr3x restarting reader");
transmitter.kill();
try {
locking.wait(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
read(callbackContext);
}
});
/* Set the reset complete callback */
mReader.setOnResetCompleteListener(new AudioJackReader.OnResetCompleteListener() {
#Override
public void onResetComplete(AudioJackReader reader) {
System.out.println("acr3x reset complete");
/* If this is the first reset, the ACR35 reader must be turned off and back on again
to work reliably... */
Thread t = null;
if(firstReset){ //firstReset
t = new Thread(new Runnable() {
public void run() {
try{
/* Set the reader asleep */
mReader.sleep();
/* Wait one second */
Thread.sleep(500);
/* Reset the reader */
mReader.reset();
firstReset = false;
} catch (InterruptedException e) {
e.printStackTrace();
// TODO: add exception handling
}
}
});
} else {
/* Create a new transmitter for the UID read command */
transmitter = new Acr3xTransmitter(mReader, mAudioManager, timeout,
apdu, acr3xCardType, locking);
t = new Thread(transmitter);
}
t.start();
}
});
mReader.start();
mReader.reset();
System.out.println("acr3x setup complete");
}
public void stop(){
if(transmitter != null){
transmitter.kill();
}
System.out.println("acr3x restoring audio level: " + acr3xStartAudioLevel);
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,acr3xStartAudioLevel, 0);
System.out.println("acr3x set audio stream level: " + mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
if(mReader != null){
mReader.stop();
}
}
}
Transmitter class
import com.acs.audiojack.AudioJackReader;
import android.media.AudioManager;
/**
* This class sets up an independent thread for card polling, and is linked to the
* <code>setOnPiccResponseApduAvailableListener</code> callback function
*/
public class Acr3xTransmitter implements Runnable {
private AudioJackReader mReader;
private AudioManager mAudioManager;
//private CallbackContext mContext;
private boolean killMe = false; /** Stop the polling thread? */
private int itersWithoutResponse = 0; /** The number of iterations that have passed with no
response from the reader */
private boolean readerConnected = true; /** Is the reader currently connected? */
private int cardType;
private int timeout;
private byte[] apdu;
private Object locking;
/**
* #param mReader: AudioJack reader service
* #param mAudioManager: system audio service
* #param mContext: context for plugin results
* #param timeout: time in <b>seconds</b> to wait for commands to complete
* #param apdu: byte array containing the command to be sent
* #param cardType: the integer representing card type
*/
public Acr3xTransmitter(AudioJackReader mReader, AudioManager mAudioManager,
int timeout, byte[] apdu, int cardType, Object locking){
this.mReader = mReader;
this.mAudioManager = mAudioManager;
this.timeout = timeout;
this.apdu = apdu;
this.cardType = cardType;
this.locking = locking;
}
/**
* Stops the polling thread
*/
public void kill(){
killMe = true;
}
/**
* Updates the connection status of the reader (links to APDU response callback)
*/
public void updateStatus(boolean status){
readerConnected = status;
}
/**
* Sends the APDU command for reading a card UID every second
*/
#Override
public void run() {
try {
/* Wait one second for stability */
Thread.sleep(1000);
while (!killMe) {
synchronized(locking){
if(killMe){
continue;
}
/* If the reader is not connected, increment no. of iterations without response */
if(!readerConnected){
itersWithoutResponse++;
}
/* Else, reset the number of iterations without a response */
else{
itersWithoutResponse = 0;
}
/* Reset the connection state */
readerConnected = false;
if(itersWithoutResponse == 4) {
/* Communicate to the Cordova application that the reader is disconnected */
System.out.println("acr3x disconnected");
/* Kill this thread */
kill();
} else if(!mAudioManager.isWiredHeadsetOn()) {
System.out.println("acr3x not connected");
/* Kill this thread */
kill();
} else{
System.out.println("acr3x reading...");
/* Power on the PICC */
mReader.piccPowerOn(timeout, cardType);
/* Transmit the APDU */
mReader.piccTransmit(timeout, apdu);
}
}
/* Repeat every second */
Thread.sleep(1000);
}
/* Power off the PICC */
mReader.piccPowerOff();
/* Set the reader asleep */
mReader.sleep();
/* Stop the reader service */
mReader.stop();
synchronized(locking){
locking.notifyAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
// TODO: add exception handling
}
}
}
listener interface:
public interface Acr3xNotifListener {
public void onUUIDAavailable(String uuid);
public void onFirmwareVersionAvailable(String firmwareVersion);
}
First you call the Reset command to activate the device. As the device will fall asleep (after 4 seconds by default)* the trick is to keeping it alive.
You can achieve that by relaunching a new PowerOn command every time the previous PowerOn timedOut. Something like that
void powerOn () {
if (!mReader.piccPowerOn(mPiccTimeout, mPiccCardType)) {
powerOn();
} else {
askNfcForItsId();
}
Don't forget to call powerOn after you read the nfc otherwise it will fall asleep after the 1st nfc card.
*the reset timeout can be set between 4 and 20 seconds
My requirement is like this: Say I am calling a number on that time and I want to call another number programmatically. So far what I have done is: I am able to call to a particular number while some call is already going. For example, suppose I am calling on number 123 and after 1 minute (by using Alarm Manger I trigger an event to call on another number 456 and that is done!
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:456"));
startActivity(intent);
I am using such intent to call and now I am able to see the screen on my phone with a button to merge the calls:
In this image you can see a button of Merging calls. Now when the user clicks on merge, it will merge all 3 Calls. I want to do it programmatically, not with the user interface.
Your question seemed interesting so I started digging in Android Source. Here is what I found:
The activity in the picture you posted is called InCallUI
When you start looking around you will find InCallPresenter which at line 463 has:
final boolean canMerge = activeCall.can(Capabilities.MERGE_CALLS);
and then at 472:
CallCommandClient.getInstance().merge();
when you check that merge() method in CallCommandClient you will find it uses ICallCommandService interface which I think is what you where looking for :)
Initialization of that CallCommandClient is in CallHandlerService around line 193.
Hope this helps & good luck.
PS. The APIs I listed are mostly internal Android stuff. You may have to use reflection to call it or it might not be possible at all - it may be inaccesible for your app because it's not marked as system app.
Android API doesn't support call merging facility you can see this thread for this.
https://groups.google.com/forum/?fromgroups#!searchin/android-developers/conference$20call/android-developers/6OXDEe0tCks/8cuKdW1J9b8J
but what you can do is open phone's call pad screen using aidl from there user can add another call or merge the call.
You cannot manage a conference with a smart phone. You need an intermediate service that can do this for you. You can program a conference manager using CCXML.
Voxeo has a good hosted platform for CCXML implementations and you can look at their documentation on how to setup conferencing. There are examples in "Learning CCXML 1.0\Multi-Party Conferencing in CCXML 1.0".
You can develop and test for free on Voxeo and they only start charging you if you put it into production. Another option is Twillio.
Here is a link to how you program a conference call on their platform.
Check the links you will get useful information. #courtesy- SO
Afaik, There is no API in the SDK which do merge call programmatically.
You have to work on the RIL (Radio Interface Layer) for Call Conference which android use for telephony calls.
Android's Radio Interface Layer (RIL) provides an abstraction layer between Android telephony services (android.telephony) and radio hardware. The RIL is radio agnostic, and includes support for Global System for Mobile communication (GSM)-based radios.
See here : http://www.kandroid.org/online-pdk/guide/telephony.html
Update
How does Modem code talk to Android code
http://fabiensanglard.net/cellphoneModem/index2.php
http://www.e-consystems.com/blog/android/?p=498
So you have to write the AT modem commands in the socket then rild invoke callback to the vendor library, then vendor library in turn delegates to the radio firmware.
After lots of search i got success in merging call, here i would like to share my finding with you.
For reference i used this link
Use CallList.java in you project
package com.example.confrencecalldemo;
import android.os.Handler;
import android.os.Message;
import android.os.Trace;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
public class CallList {
private static final int DISCONNECTED_CALL_SHORT_TIMEOUT_MS = 200;
private static final int DISCONNECTED_CALL_MEDIUM_TIMEOUT_MS = 2000;
private static final int DISCONNECTED_CALL_LONG_TIMEOUT_MS = 5000;
private static final int EVENT_DISCONNECTED_TIMEOUT = 1;
private static CallList sInstance = new CallList();
private final HashMap<String, CallHelper> mCallById = new HashMap<>();
private final HashMap<android.telecom.Call, CallHelper> mCallByTelecommCall = new HashMap<>();
private final HashMap<String, List<String>> mCallTextReponsesMap = Maps.newHashMap();
/**
* ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
* load factor before resizing, 1 means we only expect a single thread to
* access the map so make only a single shard
*/
private final Set<Listener> mListeners = Collections.newSetFromMap(
new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
private final HashMap<String, List<CallUpdateListener>> mCallUpdateListenerMap = Maps
.newHashMap();
private final Set<CallHelper> mPendingDisconnectCalls = Collections.newSetFromMap(
new ConcurrentHashMap<CallHelper, Boolean>(8, 0.9f, 1));
/**
* Static singleton accessor method.
*/
public static CallList getInstance() {
return sInstance;
}
/**
* USED ONLY FOR TESTING
* Testing-only constructor. Instance should only be acquired through getInstance().
*/
CallList() {
}
public void onCallAdded(android.telecom.Call telecommCall) {
Trace.beginSection("onCallAdded");
CallHelper call = new CallHelper(telecommCall);
// Log.d(this, "onCallAdded: callState=" + call.getState());
if (call.getState() == CallHelper.State.INCOMING ||
call.getState() == CallHelper.State.CALL_WAITING) {
onIncoming(call, call.getCannedSmsResponses());
} else {
onUpdate(call);
}
Trace.endSection();
}
public void onCallRemoved(android.telecom.Call telecommCall) {
if (mCallByTelecommCall.containsKey(telecommCall)) {
CallHelper call = mCallByTelecommCall.get(telecommCall);
if (updateCallInMap(call)) {
// Log.w(this, "Removing call not previously disconnected " + call.getId());
}
updateCallTextMap(call, null);
}
}
/**
* Called when a single call disconnects.
*/
public void onDisconnect(CallHelper call) {
if (updateCallInMap(call)) {
// Log.i(this, "onDisconnect: " + call);
// notify those listening for changes on this specific change
notifyCallUpdateListeners(call);
// notify those listening for all disconnects
notifyListenersOfDisconnect(call);
}
}
/**
* Called when a single call has changed.
*/
public void onIncoming(CallHelper call, List<String> textMessages) {
if (updateCallInMap(call)) {
// Log.i(this, "onIncoming - " + call);
}
updateCallTextMap(call, textMessages);
for (Listener listener : mListeners) {
listener.onIncomingCall(call);
}
}
public void onUpgradeToVideo(CallHelper call){
// Log.d(this, "onUpgradeToVideo call=" + call);
for (Listener listener : mListeners) {
listener.onUpgradeToVideo(call);
}
}
/**
* Called when a single call has changed.
*/
public void onUpdate(CallHelper call) {
Trace.beginSection("onUpdate");
onUpdateCall(call);
notifyGenericListeners();
Trace.endSection();
}
/**
* Called when a single call has changed session modification state.
*
* #param call The call.
* #param sessionModificationState The new session modification state.
*/
public void onSessionModificationStateChange(CallHelper call, int sessionModificationState) {
final List<CallUpdateListener> listeners = mCallUpdateListenerMap.get(call.getId());
if (listeners != null) {
for (CallUpdateListener listener : listeners) {
listener.onSessionModificationStateChange(sessionModificationState);
}
}
}
/**
* Called when the last forwarded number changes for a call. With IMS, the last forwarded
* number changes due to a supplemental service notification, so it is not pressent at the
* start of the call.
*
* #param call The call.
*/
public void onLastForwardedNumberChange(CallHelper call) {
final List<CallUpdateListener> listeners = mCallUpdateListenerMap.get(call.getId());
if (listeners != null) {
for (CallUpdateListener listener : listeners) {
listener.onLastForwardedNumberChange();
}
}
}
/**
* Called when the child number changes for a call. The child number can be received after a
* call is initially set up, so we need to be able to inform listeners of the change.
*
* #param call The call.
*/
public void onChildNumberChange(CallHelper call) {
final List<CallUpdateListener> listeners = mCallUpdateListenerMap.get(call.getId());
if (listeners != null) {
for (CallUpdateListener listener : listeners) {
listener.onChildNumberChange();
}
}
}
public void notifyCallUpdateListeners(CallHelper call) {
final List<CallUpdateListener> listeners = mCallUpdateListenerMap.get(call.getId());
if (listeners != null) {
for (CallUpdateListener listener : listeners) {
listener.onCallChanged(call);
}
}
}
/**
* Add a call update listener for a call id.
*
* #param callId The call id to get updates for.
* #param listener The listener to add.
*/
public void addCallUpdateListener(String callId, CallUpdateListener listener) {
List<CallUpdateListener> listeners = mCallUpdateListenerMap.get(callId);
if (listeners == null) {
listeners = new CopyOnWriteArrayList<CallUpdateListener>();
mCallUpdateListenerMap.put(callId, listeners);
}
listeners.add(listener);
}
/**
* Remove a call update listener for a call id.
*
* #param callId The call id to remove the listener for.
* #param listener The listener to remove.
*/
public void removeCallUpdateListener(String callId, CallUpdateListener listener) {
List<CallUpdateListener> listeners = mCallUpdateListenerMap.get(callId);
if (listeners != null) {
listeners.remove(listener);
}
}
public void addListener(Listener listener) {
Preconditions.checkNotNull(listener);
mListeners.add(listener);
// Let the listener know about the active calls immediately.
listener.onCallListChange(this);
}
public void removeListener(Listener listener) {
if (listener != null) {
mListeners.remove(listener);
}
}
/**
* TODO: Change so that this function is not needed. Instead of assuming there is an active
* call, the code should rely on the status of a specific CallHelper and allow the presenters to
* update the CallHelper object when the active call changes.
*/
public CallHelper getIncomingOrActive() {
CallHelper retval = getIncomingCall();
if (retval == null) {
retval = getActiveCall();
}
return retval;
}
public CallHelper getOutgoingOrActive() {
CallHelper retval = getOutgoingCall();
if (retval == null) {
retval = getActiveCall();
}
return retval;
}
/**
* A call that is waiting for {#link PhoneAccount} selection
*/
public CallHelper getWaitingForAccountCall() {
return getFirstCallWithState(CallHelper.State.SELECT_PHONE_ACCOUNT);
}
public CallHelper getPendingOutgoingCall() {
return getFirstCallWithState(CallHelper.State.CONNECTING);
}
public CallHelper getOutgoingCall() {
CallHelper call = getFirstCallWithState(CallHelper.State.DIALING);
if (call == null) {
call = getFirstCallWithState(CallHelper.State.REDIALING);
}
return call;
}
public CallHelper getActiveCall() {
return getFirstCallWithState(CallHelper.State.ACTIVE);
}
public CallHelper getBackgroundCall() {
return getFirstCallWithState(CallHelper.State.ONHOLD);
}
public CallHelper getDisconnectedCall() {
return getFirstCallWithState(CallHelper.State.DISCONNECTED);
}
public CallHelper getDisconnectingCall() {
return getFirstCallWithState(CallHelper.State.DISCONNECTING);
}
public CallHelper getSecondBackgroundCall() {
return getCallWithState(CallHelper.State.ONHOLD, 1);
}
public CallHelper getActiveOrBackgroundCall() {
CallHelper call = getActiveCall();
if (call == null) {
call = getBackgroundCall();
}
return call;
}
public CallHelper getIncomingCall() {
CallHelper call = getFirstCallWithState(CallHelper.State.INCOMING);
if (call == null) {
call = getFirstCallWithState(CallHelper.State.CALL_WAITING);
}
return call;
}
public CallHelper getFirstCall() {
CallHelper result = getIncomingCall();
if (result == null) {
result = getPendingOutgoingCall();
}
if (result == null) {
result = getOutgoingCall();
}
if (result == null) {
result = getFirstCallWithState(CallHelper.State.ACTIVE);
}
if (result == null) {
result = getDisconnectingCall();
}
if (result == null) {
result = getDisconnectedCall();
}
return result;
}
public boolean hasLiveCall() {
CallHelper call = getFirstCall();
if (call == null) {
return false;
}
return call != getDisconnectingCall() && call != getDisconnectedCall();
}
public CallHelper getVideoUpgradeRequestCall() {
for(CallHelper call : mCallById.values()) {
if (call.getSessionModificationState() ==
CallHelper.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
return call;
}
}
return null;
}
public CallHelper getCallById(String callId) {
return mCallById.get(callId);
}
public CallHelper getCallByTelecommCall(android.telecom.Call telecommCall) {
return mCallByTelecommCall.get(telecommCall);
}
public List<String> getTextResponses(String callId) {
return mCallTextReponsesMap.get(callId);
}
/**
* Returns first call found in the call map with the specified state.
*/
public CallHelper getFirstCallWithState(int state) {
return getCallWithState(state, 0);
}
/**
* Returns the [position]th call found in the call map with the specified state.
* TODO: Improve this logic to sort by call time.
*/
public CallHelper getCallWithState(int state, int positionToFind) {
CallHelper retval = null;
int position = 0;
for (CallHelper call : mCallById.values()) {
if (call.getState() == state) {
if (position >= positionToFind) {
retval = call;
break;
} else {
position++;
}
}
}
return retval;
}
/**
* This is called when the service disconnects, either expectedly or unexpectedly.
* For the expected case, it's because we have no calls left. For the unexpected case,
* it is likely a crash of phone and we need to clean up our calls manually. Without phone,
* there can be no active calls, so this is relatively safe thing to do.
*/
public void clearOnDisconnect() {
for (CallHelper call : mCallById.values()) {
final int state = call.getState();
if (state != CallHelper.State.IDLE &&
state != CallHelper.State.INVALID &&
state != CallHelper.State.DISCONNECTED) {
call.setState(CallHelper.State.DISCONNECTED);
call.setDisconnectCause(new DisconnectCause(DisconnectCause.UNKNOWN));
updateCallInMap(call);
}
}
notifyGenericListeners();
}
/**
* Called when the user has dismissed an error dialog. This indicates acknowledgement of
* the disconnect cause, and that any pending disconnects should immediately occur.
*/
public void onErrorDialogDismissed() {
final Iterator<CallHelper> iterator = mPendingDisconnectCalls.iterator();
while (iterator.hasNext()) {
CallHelper call = iterator.next();
iterator.remove();
finishDisconnectedCall(call);
}
}
/**
* Processes an update for a single call.
*
* #param call The call to update.
*/
private void onUpdateCall(CallHelper call) {
// Log.d(this, "\t" + call);
if (updateCallInMap(call)) {
// Log.i(this, "onUpdate - " + call);
}
updateCallTextMap(call, call.getCannedSmsResponses());
notifyCallUpdateListeners(call);
}
/**
* Sends a generic notification to all listeners that something has changed.
* It is up to the listeners to call back to determine what changed.
*/
private void notifyGenericListeners() {
for (Listener listener : mListeners) {
listener.onCallListChange(this);
}
}
private void notifyListenersOfDisconnect(CallHelper call) {
for (Listener listener : mListeners) {
listener.onDisconnect(call);
}
}
/**
* Updates the call entry in the local map.
* #return false if no call previously existed and no call was added, otherwise true.
*/
private boolean updateCallInMap(CallHelper call) {
Preconditions.checkNotNull(call);
boolean updated = false;
if (call.getState() == CallHelper.State.DISCONNECTED) {
// update existing (but do not add!!) disconnected calls
if (mCallById.containsKey(call.getId())) {
// For disconnected calls, we want to keep them alive for a few seconds so that the
// UI has a chance to display anything it needs when a call is disconnected.
// Set up a timer to destroy the call after X seconds.
final Message msg = mHandler.obtainMessage(EVENT_DISCONNECTED_TIMEOUT, call);
mHandler.sendMessageDelayed(msg, getDelayForDisconnect(call));
mPendingDisconnectCalls.add(call);
mCallById.put(call.getId(), call);
mCallByTelecommCall.put(call.getTelecommCall(), call);
updated = true;
}
} else if (!isCallDead(call)) {
mCallById.put(call.getId(), call);
mCallByTelecommCall.put(call.getTelecommCall(), call);
updated = true;
} else if (mCallById.containsKey(call.getId())) {
mCallById.remove(call.getId());
mCallByTelecommCall.remove(call.getTelecommCall());
updated = true;
}
return updated;
}
private int getDelayForDisconnect(CallHelper call) {
Preconditions.checkState(call.getState() == CallHelper.State.DISCONNECTED);
final int cause = call.getDisconnectCause().getCode();
final int delay;
switch (cause) {
case DisconnectCause.LOCAL:
delay = DISCONNECTED_CALL_SHORT_TIMEOUT_MS;
break;
case DisconnectCause.REMOTE:
case DisconnectCause.ERROR:
delay = DISCONNECTED_CALL_MEDIUM_TIMEOUT_MS;
break;
case DisconnectCause.REJECTED:
case DisconnectCause.MISSED:
case DisconnectCause.CANCELED:
// no delay for missed/rejected incoming calls and canceled outgoing calls.
delay = 0;
break;
default:
delay = DISCONNECTED_CALL_LONG_TIMEOUT_MS;
break;
}
return delay;
}
private void updateCallTextMap(CallHelper call, List<String> textResponses) {
Preconditions.checkNotNull(call);
if (!isCallDead(call)) {
if (textResponses != null) {
mCallTextReponsesMap.put(call.getId(), textResponses);
}
} else if (mCallById.containsKey(call.getId())) {
mCallTextReponsesMap.remove(call.getId());
}
}
private boolean isCallDead(CallHelper call) {
final int state = call.getState();
return CallHelper.State.IDLE == state || CallHelper.State.INVALID == state;
}
/**
* Sets up a call for deletion and notifies listeners of change.
*/
private void finishDisconnectedCall(CallHelper call) {
if (mPendingDisconnectCalls.contains(call)) {
mPendingDisconnectCalls.remove(call);
}
call.setState(CallHelper.State.IDLE);
updateCallInMap(call);
notifyGenericListeners();
}
/**
* Notifies all video calls of a change in device orientation.
*
* #param rotation The new rotation angle (in degrees).
*/
public void notifyCallsOfDeviceRotation(int rotation) {
for (CallHelper call : mCallById.values()) {
// First, ensure a VideoCall is set on the call so that the change can be sent to the
// provider (a VideoCall can be present for a call that does not currently have video,
// but can be upgraded to video).
// Second, ensure that the call videoState has video enabled (there is no need to set
// device orientation on a voice call which has not yet been upgraded to video).
if (call.getVideoCall() != null && CallUtils.isVideoCall(call)) {
call.getVideoCall().setDeviceOrientation(rotation);
}
}
}
/**
* Handles the timeout for destroying disconnected calls.
*/
private Handler mHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_DISCONNECTED_TIMEOUT:
// Log.d(this, "EVENT_DISCONNECTED_TIMEOUT ", msg.obj);
finishDisconnectedCall((CallHelper) msg.obj);
break;
default:
// Log.wtf(this, "Message not expected: " + msg.what);
break;
}
}
};
/**
* Listener interface for any class that wants to be notified of changes
* to the call list.
*/
public interface Listener {
/**
* Called when a new incoming call comes in.
* This is the only method that gets called for incoming calls. Listeners
* that want to perform an action on incoming call should respond in this method
* because {#link #onCallListChange} does not automatically get called for
* incoming calls.
*/
public void onIncomingCall(CallHelper call);
/**
* Called when a new modify call request comes in
* This is the only method that gets called for modify requests.
*/
public void onUpgradeToVideo(CallHelper call);
/**
* Called anytime there are changes to the call list. The change can be switching call
* states, updating information, etc. This method will NOT be called for new incoming
* calls and for calls that switch to disconnected state. Listeners must add actions
* to those method implementations if they want to deal with those actions.
*/
public void onCallListChange(CallList callList);
/**
* Called when a call switches to the disconnected state. This is the only method
* that will get called upon disconnection.
*/
public void onDisconnect(CallHelper call);
}
public interface CallUpdateListener {
// TODO: refactor and limit arg to be call state. Caller info is not needed.
public void onCallChanged(CallHelper call);
/**
* Notifies of a change to the session modification state for a call.
*
* #param sessionModificationState The new session modification state.
*/
public void onSessionModificationStateChange(int sessionModificationState);
/**
* Notifies of a change to the last forwarded number for a call.
*/
public void onLastForwardedNumberChange();
/**
* Notifies of a change to the child number for a call.
*/
public void onChildNumberChange();
}
}
2.Call methods of CallList.java from InCallService class.
#Override
public void onCallAdded(Call call) {
super.onCallAdded(call);
Log.d("MyConnectionService","onCallAdded");
CallList.getInstance().onCallAdded(call);
}
#Override
public void onCallRemoved(Call call) {
super.onCallRemoved(call);
Log.d("MyConnectionService","onCallRemoved");
CallList.getInstance().onCallRemoved(call);
}
finally call function to merge call
public void mergeCall() {
final CallList calls = CallList.getInstance();
CallHelper activeCall = calls.getActiveCall();
if (activeCall != null) {
final boolean canMerge = activeCall.can(
android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE);
final boolean canSwap = activeCall.can(
android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE);
// (2) Attempt actions on conference calls
if (canMerge) {
TelecomAdapter.getInstance().merge(activeCall.getId());
} else if (canSwap) {
TelecomAdapter.getInstance().swap(activeCall.getId());
}
}
}
I am editing my answer, i forgot to place CallHelper.java class.
Please visit below link for CallHelper.java file.
https://gist.github.com/amitsemwal1/4e9ca712adc8daaf070a0cc0e0d58c26
There is no api for getting conference call in android, you may want to play with the root system and get your work done.
officially android is not providing any api for conference call. You can study more for root access play here
http://www.kandroid.org/online-pdk/guide/telephony.html
I need to send file to a computer instead of another android application. I have looked at the bluetooth api, but it only allow connection as client-server. In my case I dont know what UUId would be on the computer. Do I need to look at obex. I haven't used it before. So any help would be benficial.
Try this.
I can send a file using this code.
ContentValues values = new ContentValues();
values.put(BluetoothShare.URI, "file:///sdcard/refresh.txt");
values.put(BluetoothShare.DESTINATION, deviceAddress);
values.put(BluetoothShare.DIRECTION, BluetoothShare.DIRECTION_OUTBOUND);
Long ts = System.currentTimeMillis();
values.put(BluetoothShare.TIMESTAMP, ts);
getContentResolver().insert(BluetoothShare.CONTENT_URI, values);
Code of BluetoothShare.java
import android.provider.BaseColumns;
import android.net.Uri;
/**
* Exposes constants used to interact with the Bluetooth Share manager's content
* provider.
*/
public final class BluetoothShare implements BaseColumns {
private BluetoothShare() {
}
/**
* The permission to access the Bluetooth Share Manager
*/
public static final String PERMISSION_ACCESS = "android.permission.ACCESS_BLUETOOTH_SHARE";
/**
* The content:// URI for the data table in the provider
*/
public static final Uri CONTENT_URI = Uri.parse("content://com.android.bluetooth.opp/btopp");
/**
* Broadcast Action: this is sent by the Bluetooth Share component to
* transfer complete. The request detail could be retrieved by app * as _ID
* is specified in the intent's data.
*/
public static final String TRANSFER_COMPLETED_ACTION = "android.btopp.intent.action.TRANSFER_COMPLETE";
/**
* This is sent by the Bluetooth Share component to indicate there is an
* incoming file need user to confirm.
*/
public static final String INCOMING_FILE_CONFIRMATION_REQUEST_ACTION = "android.btopp.intent.action.INCOMING_FILE_NOTIFICATION";
/**
* This is sent by the Bluetooth Share component to indicate there is an
* incoming file request timeout and need update UI.
*/
public static final String USER_CONFIRMATION_TIMEOUT_ACTION = "android.btopp.intent.action.USER_CONFIRMATION_TIMEOUT";
/**
* The name of the column containing the URI of the file being
* sent/received.
*/
public static final String URI = "uri";
/**
* The name of the column containing the filename that the incoming file
* request recommends. When possible, the Bluetooth Share manager will
* attempt to use this filename, or a variation, as the actual name for the
* file.
*/
public static final String FILENAME_HINT = "hint";
/**
* The name of the column containing the filename where the shared file was
* actually stored.
*/
public static final String _DATA = "_data";
/**
* The name of the column containing the MIME type of the shared file.
*/
public static final String MIMETYPE = "mimetype";
/**
* The name of the column containing the direction (Inbound/Outbound) of the
* transfer. See the DIRECTION_* constants for a list of legal values.
*/
public static final String DIRECTION = "direction";
/**
* The name of the column containing Bluetooth Device Address that the
* transfer is associated with.
*/
public static final String DESTINATION = "destination";
/**
* The name of the column containing the flags that controls whether the
* transfer is displayed by the UI. See the VISIBILITY_* constants for a
* list of legal values.
*/
public static final String VISIBILITY = "visibility";
/**
* The name of the column containing the current user confirmation state of
* the transfer. Applications can write to this to confirm the transfer. the
* USER_CONFIRMATION_* constants for a list of legal values.
*/
public static final String USER_CONFIRMATION = "confirm";
/**
* The name of the column containing the current status of the transfer.
* Applications can read this to follow the progress of each download. See
* the STATUS_* constants for a list of legal values.
*/
public static final String STATUS = "status";
/**
* The name of the column containing the total size of the file being
* transferred.
*/
public static final String TOTAL_BYTES = "total_bytes";
/**
* The name of the column containing the size of the part of the file that
* has been transferred so far.
*/
public static final String CURRENT_BYTES = "current_bytes";
/**
* The name of the column containing the timestamp when the transfer is
* initialized.
*/
public static final String TIMESTAMP = "timestamp";
/**
* This transfer is outbound, e.g. share file to other device.
*/
public static final int DIRECTION_OUTBOUND = 0;
/**
* This transfer is inbound, e.g. receive file from other device.
*/
public static final int DIRECTION_INBOUND = 1;
/**
* This transfer is waiting for user confirmation.
*/
public static final int USER_CONFIRMATION_PENDING = 0;
/**
* This transfer is confirmed by user.
*/
public static final int USER_CONFIRMATION_CONFIRMED = 1;
/**
* This transfer is auto-confirmed per previous user confirmation.
*/
public static final int USER_CONFIRMATION_AUTO_CONFIRMED = 2;
/**
* This transfer is denied by user.
*/
public static final int USER_CONFIRMATION_DENIED = 3;
/**
* This transfer is timeout before user action.
*/
public static final int USER_CONFIRMATION_TIMEOUT = 4;
/**
* This transfer is visible and shows in the notifications while in progress
* and after completion.
*/
public static final int VISIBILITY_VISIBLE = 0;
/**
* This transfer doesn't show in the notifications.
*/
public static final int VISIBILITY_HIDDEN = 1;
/**
* Returns whether the status is informational (i.e. 1xx).
*/
public static boolean isStatusInformational(int status) {
return (status >= 100 && status < 200);
}
/**
* Returns whether the transfer is suspended. (i.e. whether the transfer
* won't complete without some action from outside the transfer manager).
*/
public static boolean isStatusSuspended(int status) {
return (status == STATUS_PENDING);
}
/**
* Returns whether the status is a success (i.e. 2xx).
*/
public static boolean isStatusSuccess(int status) {
return (status >= 200 && status < 300);
}
/**
* Returns whether the status is an error (i.e. 4xx or 5xx).
*/
public static boolean isStatusError(int status) {
return (status >= 400 && status < 600);
}
/**
* Returns whether the status is a client error (i.e. 4xx).
*/
public static boolean isStatusClientError(int status) {
return (status >= 400 && status < 500);
}
/**
* Returns whether the status is a server error (i.e. 5xx).
*/
public static boolean isStatusServerError(int status) {
return (status >= 500 && status < 600);
}
/**
* Returns whether the transfer has completed (either with success or
* error).
*/
public static boolean isStatusCompleted(int status) {
return (status >= 200 && status < 300) || (status >= 400 && status < 600);
}
/**
* This transfer hasn't stated yet
*/
public static final int STATUS_PENDING = 190;
/**
* This transfer has started
*/
public static final int STATUS_RUNNING = 192;
/**
* This transfer has successfully completed. Warning: there might be other
* status values that indicate success in the future. Use isSucccess() to
* capture the entire category.
*/
public static final int STATUS_SUCCESS = 200;
/**
* This request couldn't be parsed. This is also used when processing
* requests with unknown/unsupported URI schemes.
*/
public static final int STATUS_BAD_REQUEST = 400;
/**
* This transfer is forbidden by target device.
*/
public static final int STATUS_FORBIDDEN = 403;
/**
* This transfer can't be performed because the content cannot be handled.
*/
public static final int STATUS_NOT_ACCEPTABLE = 406;
/**
* This transfer cannot be performed because the length cannot be determined
* accurately. This is the code for the HTTP error "Length Required", which
* is typically used when making requests that require a content length but
* don't have one, and it is also used in the client when a response is
* received whose length cannot be determined accurately (therefore making
* it impossible to know when a transfer completes).
*/
public static final int STATUS_LENGTH_REQUIRED = 411;
/**
* This transfer was interrupted and cannot be resumed. This is the code for
* the OBEX error "Precondition Failed", and it is also used in situations
* where the client doesn't have an ETag at all.
*/
public static final int STATUS_PRECONDITION_FAILED = 412;
/**
* This transfer was canceled
*/
public static final int STATUS_CANCELED = 490;
/**
* This transfer has completed with an error. Warning: there will be other
* status values that indicate errors in the future. Use isStatusError() to
* capture the entire category.
*/
public static final int STATUS_UNKNOWN_ERROR = 491;
/**
* This transfer couldn't be completed because of a storage issue.
* Typically, that's because the file system is missing or full.
*/
public static final int STATUS_FILE_ERROR = 492;
/**
* This transfer couldn't be completed because of no sdcard.
*/
public static final int STATUS_ERROR_NO_SDCARD = 493;
/**
* This transfer couldn't be completed because of sdcard full.
*/
public static final int STATUS_ERROR_SDCARD_FULL = 494;
/**
* This transfer couldn't be completed because of an unspecified un-handled
* OBEX code.
*/
public static final int STATUS_UNHANDLED_OBEX_CODE = 495;
/**
* This transfer couldn't be completed because of an error receiving or
* processing data at the OBEX level.
*/
public static final int STATUS_OBEX_DATA_ERROR = 496;
/**
* This transfer couldn't be completed because of an error when establishing
* connection.
*/
public static final int STATUS_CONNECTION_ERROR = 497;
}
For Ice Cream Sandwich this code is not working so you have to use this code
int currentapiVersion = android.os.Build.VERSION.SDK_INT;
if (currentapiVersion >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
Intent sharingIntent = new Intent(
android.content.Intent.ACTION_SEND);
sharingIntent.setType("image/jpeg");
sharingIntent
.setComponent(new ComponentName(
"com.android.bluetooth",
"com.android.bluetooth.opp.BluetoothOppLauncherActivity"));
sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
startActivity(sharingIntent);
} else {
ContentValues values = new ContentValues();
values.put(BluetoothShare.URI, uri.toString());
Toast.makeText(getBaseContext(), "URi : " + uri,
Toast.LENGTH_LONG).show();
values.put(BluetoothShare.DESTINATION, deviceAddress);
values.put(BluetoothShare.DIRECTION,
BluetoothShare.DIRECTION_OUTBOUND);
Long ts = System.currentTimeMillis();
values.put(BluetoothShare.TIMESTAMP, ts);
getContentResolver().insert(BluetoothShare.CONTENT_URI,
values);
}
You can use the obex library. It seemed that android didn't provide the obex library, but I solved the problem and the solution is posted here.
Further Explanation (please start reading from here if you're busy)
I was trying to create an android phone remote controller (and something similar to telnet server) which helps controlling the phone remotely with my old feature phone.
Main content :Bluetooth FTP client
My first plan was to make the app check the list of files of my feature phone's directory.
But I didn't know how to connect to my feature phone's ftp server.
I googled a lot about how to connect to a ftp server via bluetooth but I could only find that Bluetoorh FTP server used the OBEX Protocol.
I found a useful material (PDF file) in a SO thread and studied about OBEX connect requests, put and get operations.
So I finally wrote some codes that tries to connect to the Bluetooth FTP server. I want to show them to you, but I lost it :( The codes were like just directly writting byte sequences to the output stream.
I also had difficult time finding out what UUID makes the app connect as FTP client. But I tried every UUIDs retrieved using the code below.
String parcels="";
ParcelUuid[] uuids=mBtDevice.getUuids();
int i=0;
for (ParcelUuid p:uuids)
{
parcels += "UUID UUID" + new Integer(i).toString() + "=UUID.fromString((\"" + p.getUuid().toString() + "\"));\n\n";
++i;
}
Nothing seemed to bring me to the answer I wanted. So I googled more and found out that I not only should I use UUID 00001106-0000-1000-8000-00805f9b34fb to connect to OBEX FTP server, but also shold I transmit target header ** with UUID **F9EC7BC4-953C-11D2-984E-525400DC9E09 when sending OBEX connect request.
The code below shows how to connect to a bluetooth FTP server as a client.
try
{
mBtSocket = mBtDevice.createInsecureRfcommSocketToServiceRecord(UUID.fromString(" 00001106-0000-1000-8000-00805f9b34fb"));
}
catch (Exception e)
{
//e.printStackTrace();
}
Thread thread=new Thread(new Runnable() {
public void run()
{
UUID uuid=UUID.fromString("F9EC7BC4-953C-11D2-984E-525400DC9E09");
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
bb.putLong(uuid.getMostSignificantBits());
bb.putLong(uuid.getLeastSignificantBits());
byte [] bytes=bb.array();
Operation putOperation=null;
Operation getOperation=null;
try
{
// connect the socket
mBtSocket.connect();
//I will explain below
mSession = new ClientSession((ObexTransport)(mTransport = new BluetoothObexTransport(mBtSocket)));
HeaderSet headerset = new HeaderSet();
headerset.setHeader(HeaderSet.TARGET, bytes);
headerset = mSession.connect(headerset);
if (headerset.getResponseCode() == ResponseCodes.OBEX_HTTP_OK)
{
mConnected = true;
}
else
{
mSession.disconnect(headerset);
}
...
Then you are now connected as FTP client and are ready to use OBEX operations to send files, request for files, list directories, etc.
However I didn't want to wait an hour to send my command to my android phone. (And it would be inefficient if I increase frequency of polling, as every polling methods are.)
Start reading from here if you are busy
Main content: OBEX OPP
For the reason I mentioned above, I greedly searched for ways to manipulate OPP which I discovered from the OBEX documentation.
You may want to transfer files via bluetooth normally (without defining your protocol and building a new desktop application just for it) to your computer, right? Then sending to OBEX OPP inbox service that is running natively on your desktop windows computer is the best solution. So how can we connect to the OPP (Obex Object Push) inbox service?
Setup OBEX library
Add import javax.obex; to your source code.
If your compiler doesn't support OBEX library, download sources and add to your project from here.
Implement ObexTransport
You should provide a class that implements ObexTransport to the library when you use it. It defines how the library should send data (like by RFCOMM, TCP,...). A sample implementation is here. This can cause some runtime or compilation errors such as there's no method. But you can partially fix those by replacing the method calls to constants like return 4096 instead of return mSocket.getMaxTransmitPacketSize();, outcommenting the if statements of public int getMaxTransmitPacketSize(). Or you can try using reflection to get those methods runtime.
Get BluetoothSocket
Get a bluetooth socket using mBtDevice.createInsecureRfcommSocketToServiceRecord(UUID.fromString(" 00001105-0000-1000-8000-00805f9b34fb" )); And call connect().
Create ClientSession
Create a instance of your ObexTransport implementation, and create a new ClientSession like mSession = new ClientSession((ObexTransport)(mTransport = new BluetoothObexTransport(mBtSocket)));.
Send OBEX connect request to your computer OPP inbox service.
HeaderSet headerset = new HeaderSet();
// headerset.setHeader(HeaderSet.COUNT,n);
headerset = mSession.connect(null);
if (headerset.getResponseCode() == ResponseCodes.OBEX_HTTP_OK)
{
mConnected = true;
}
Send OBEX put requests using the ClientSession.
protected boolean Put(ClientSession session, byte[] bytes, String as, String type)
{
// TODO: Implement this method
//byte [] bytes;
String filename=as;
boolean retry=true;
int times=0;
while (retry && times < 4)
{
Operation putOperation=null;
OutputStream mOutput = null;
//ClientSession mSession = null;
//ArrayUtils.reverse(bytes);
try
{
// Send a file with meta data to the server
final HeaderSet hs = new HeaderSet();
hs.setHeader(HeaderSet.NAME, filename);
hs.setHeader(HeaderSet.TYPE, type);
hs.setHeader(HeaderSet.LENGTH, new Long((long)bytes.length));
Log.v(TAG,filename);
//Log.v(TAG,type);
Log.v(TAG,bytes.toString());
putOperation = session.put(hs);
mOutput = putOperation.openOutputStream();
mOutput.write(bytes);
mOutput.close();
putOperation.close();
}
catch (Exception e)
{
Log.e(TAG, "put failed", e);
retry = true;
times++;
continue;
//e.printStackTrace();
}
finally
{
try
{
if(mOutput!=null)
mOutput.close();
if(putOperation!=null)
putOperation.close();
}
catch (Exception e)
{
Log.e(TAG, "put finally" , e);
retry = true;
times++;
continue;
}
//updateStatus("[CLIENT] Connection Closed");
}
retry = false;
return true;
}
return false;
}
Finally, disconnect.
private void FinishBatch(ClientSession mSession) throws IOException
{
mSession.disconnect(null);
try
{
Thread.sleep((long)500);
}
catch (InterruptedException e)
{}
mBtSocket.close();
}
Then here is a wrapper class.
import android.bluetooth.*;
import android.util.*;
import java.io.*;
import java.util.*;
import javax.obex.*;
public class BluetoothOPPHelper
{
String address;
BluetoothAdapter mBtadapter;
BluetoothDevice device;
ClientSession session;
BluetoothSocket mBtSocket;
protected final UUID OPPUUID=UUID.fromString(("00001105-0000-1000-8000-00805f9b34fb"));
private String TAG="BluetoothOPPHelper";
public BluetoothOPPHelper(String address)
{
mBtadapter=BluetoothAdapter.getDefaultAdapter();
device=mBtadapter.getRemoteDevice(address);
try
{
mBtSocket = device.createRfcommSocketToServiceRecord(OPPUUID);
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
public ClientSession StartBatch(int n)
{
ClientSession mSession = null;
// TODO: Implement this method
boolean retry=true;
int times=0;
while (retry && times < 4)
{
//BluetoothConnector.BluetoothSocketWrapper bttmp=null;
try
{
mBtSocket.connect();
//bttmp = (new BluetoothConnector(device,false,BluetoothAdapter.getDefaultAdapter(),Arrays.asList(new UUID[]{OPPUUID,OPPUUID, OPPUUID}))).connect();//*/ device.createInsecureRfcommSocketToServiceRecord(OPPUUID);
/*if(mBtSocket.isConnected())
{
mBtSocket.close();
}*/
}
catch (Exception e)
{
Log.e(TAG, "opp fail sock " + e.getMessage());
retry = true;
times++;
continue;
}
try
{
//mBtSocket=bttmp.getUnderlyingSocket();
// mBtSocket.connect();
BluetoothObexTransport mTransport = null;
mSession = new ClientSession((ObexTransport)(mTransport = new BluetoothObexTransport(mBtSocket)));
HeaderSet headerset = new HeaderSet();
// headerset.setHeader(HeaderSet.COUNT,n);
headerset = mSession.connect(null);
if (headerset.getResponseCode() == ResponseCodes.OBEX_HTTP_OK)
{
boolean mConnected = true;
}
else
{
Log.e(TAG, "SEnd by OPP denied;");
mSession.disconnect(headerset);
times++;
continue;
}
}
catch (Exception e)
{
Log.e(TAG, "opp failed;" , e);
retry = true;
times++;
continue;
//e.rintStackTrace();
}
retry=false;
}
return mSession;
}
protected boolean Put(ClientSession session, byte[] bytes, String as, String type)
{
// TODO: Implement this method
//byte [] bytes;
String filename=as;
boolean retry=true;
int times=0;
while (retry && times < 4)
{
Operation putOperation=null;
OutputStream mOutput = null;
//ClientSession mSession = null;
//ArrayUtils.reverse(bytes);
try
{
// Send a file with meta data to the server
final HeaderSet hs = new HeaderSet();
hs.setHeader(HeaderSet.NAME, filename);
hs.setHeader(HeaderSet.TYPE, type);
hs.setHeader(HeaderSet.LENGTH, new Long((long)bytes.length));
Log.v(TAG,filename);
//Log.v(TAG,type);
Log.v(TAG,bytes.toString());
putOperation = session.put(hs);
mOutput = putOperation.openOutputStream();
mOutput.write(bytes);
mOutput.close();
putOperation.close();
}
catch (Exception e)
{
Log.e(TAG, "put failed", e);
retry = true;
times++;
continue;
//e.printStackTrace();
}
finally
{
try
{
if(mOutput!=null)
mOutput.close();
if(putOperation!=null)
putOperation.close();
}
catch (Exception e)
{
Log.e(TAG, "put finally" , e);
retry = true;
times++;
continue;
}
//updateStatus("[CLIENT] Connection Closed");
}
retry = false;
return true;
}
return false;
}
protected boolean Put(ClientSession s, OPPBatchInfo info)
{
return Put(s,info.data,info.as,info.type);
}
private void FinishBatch(ClientSession mSession) throws IOException
{
mSession.disconnect(null);
try
{
Thread.sleep((long)500);
}
catch (InterruptedException e)
{}
mBtSocket.close();
}
public boolean flush() throws IOException
{
if (sendQueue.isEmpty())
{
return true;
}
try
{
Thread.sleep((long)2000);
}
catch (InterruptedException e)
{}
ClientSession session=StartBatch(sendQueue.size());
if (session == null)
{
return false;
}
while (!sendQueue.isEmpty())
{
if (Put(session, sendQueue.remove()) == false)
{
Log.e(TAG, "Put failed");
}
}
FinishBatch(session);
return true;
}
Queue<OPPBatchInfo> sendQueue;
public boolean AddTransfer(String as,String mimetype,byte[] data)
{
return sendQueue.add(new OPPBatchInfo(as,mimetype,data));
}
class OPPBatchInfo
{
String as;
String type;
byte[] data;
public OPPBatchInfo(String as,String type,byte[] data)
{
this.as=as;
this.data=data;
this.type=type;
}
}
}
You need to implement FTP over OBEX. Once you implement the standard protocol and profile, your Android FTP implementation will inter-operate with virtually any Bluetooth FTP server. You'll also need to implement OPP for maximum inter-operability. The OBEX protocol is not so difficult to implement and the specs is freely available.
I know this question is old, but for anyone having to deal with this still:
With this library you can send files via OBEX and commands via RFCOMM:
https://github.com/ddibiasi/Funker
Once connected to your target device, you can manipulate its filesystem.
The following example sends a file:
val rxOBEX = RxObex(device)
rxOBEX
.putFile("rubberduck.txt", "text/plain", "oh hi mark".toByteArray(), "example/directory") // Name of file, mimetype, bytes of file, directory
.subscribeBy(
onComplete = {
Log.d(TAG, "Succesfully sent a testfile to device")
},
onError = { e ->
Log.e(TAG, "Received error!")
}
)
The library is built on Rx, so all calls are non blocking.