How to merge Call programmatically while other call is running (Conference call) - android

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

Related

How to navigate in Google VR view?

I am writing an android app using VrPanoramaView
I know there are two ways to navigate in VR apps
Use the single button on the cardboard as a click
Treat looking at something for a long period of time as a click
How can i implement navigation using one of these so that if user clicks button panoview will skip to the next picture?
Below is the sample Vr PanoramaView code
**
* A basic PanoWidget Activity to load panorama images from disk. It will load a test image by
* default. It can also load an arbitrary image from disk using:
* adb shell am start -a "android.intent.action.VIEW" \
* -n "com.google.vr.sdk.samples.simplepanowidget/.SimpleVrPanoramaActivity" \
* -d "/sdcard/FILENAME.JPG"
*
* To load stereo images, "--ei inputType 2" can be used to pass in an integer extra which will set
* VrPanoramaView.Options.inputType.
*/
public class SimpleVrPanoramaActivity extends Activity {
InputStream istr = null;
private static final String TAG = SimpleVrPanoramaActivity.class.getSimpleName();
/** Actual panorama widget. **/
private VrPanoramaView panoWidgetView;
/**
* Arbitrary variable to track load status. In this example, this variable should only be accessed
* on the UI thread. In a real app, this variable would be code that performs some UI actions when
* the panorama is fully loaded.
*/
public boolean loadImageSuccessful;
/** Tracks the file to be loaded across the lifetime of this app. **/
private Uri fileUri;
/** Configuration information for the panorama. **/
private Options panoOptions = new Options();
private ImageLoaderTask backgroundImageLoaderTask;
/**
* Called when the app is launched via the app icon or an intent using the adb command above. This
* initializes the app and loads the image to render.
*/
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
// Make the source link clickable.
TextView sourceText = (TextView) findViewById(R.id.source);
sourceText.setText(Html.fromHtml(getString(R.string.source)));
sourceText.setMovementMethod(LinkMovementMethod.getInstance());
panoWidgetView = (VrPanoramaView) findViewById(R.id.pano_view);
panoWidgetView.setEventListener(new ActivityEventListener());
// Initial launch of the app or an Activity recreation due to rotation.
handleIntent(getIntent());
}
/**
* Called when the Activity is already running and it's given a new intent.
*/
#Override
protected void onNewIntent(Intent intent) {
Log.i(TAG, this.hashCode() + ".onNewIntent()");
// Save the intent. This allows the getIntent() call in onCreate() to use this new Intent during
// future invocations.
setIntent(intent);
// Load the new image.
handleIntent(intent);
}
/**
* Load custom images based on the Intent or load the default image. See the Javadoc for this
* class for information on generating a custom intent via adb.
*/
private void handleIntent(Intent intent) {
// Determine if the Intent contains a file to load.
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
Log.i(TAG, "ACTION_VIEW Intent recieved");
fileUri = intent.getData();
if (fileUri == null) {
Log.w(TAG, "No data uri specified. Use \"-d /path/filename\".");
} else {
Log.i(TAG, "Using file " + fileUri.toString());
}
panoOptions.inputType = intent.getIntExtra("inputType", Options.TYPE_MONO);
Log.i(TAG, "Options.inputType = " + panoOptions.inputType);
} else {
Log.i(TAG, "Intent is not ACTION_VIEW. Using default pano image.");
fileUri = null;
panoOptions.inputType = Options.TYPE_MONO;
}
// Load the bitmap in a background thread to avoid blocking the UI thread. This operation can
// take 100s of milliseconds.
if (backgroundImageLoaderTask != null) {
// Cancel any task from a previous intent sent to this activity.
backgroundImageLoaderTask.cancel(true);
}
backgroundImageLoaderTask = new ImageLoaderTask();
backgroundImageLoaderTask.execute(Pair.create(fileUri, panoOptions));
}
#Override
protected void onPause() {
panoWidgetView.pauseRendering();
super.onPause();
}
#Override
protected void onResume() {
super.onResume();
panoWidgetView.resumeRendering();
}
#Override
protected void onDestroy() {
// Destroy the widget and free memory.
panoWidgetView.shutdown();
// The background task has a 5 second timeout so it can potentially stay alive for 5 seconds
// after the activity is destroyed unless it is explicitly cancelled.
if (backgroundImageLoaderTask != null) {
backgroundImageLoaderTask.cancel(true);
}
super.onDestroy();
}
/**
* Helper class to manage threading.
*/
class ImageLoaderTask extends AsyncTask<Pair<Uri, Options>, Void, Boolean> {
/**
* Reads the bitmap from disk in the background and waits until it's loaded by pano widget.
*/
#Override
protected Boolean doInBackground(Pair<Uri, Options>... fileInformation) {
Options panoOptions = null; // It's safe to use null VrPanoramaView.Options.
InputStream istr = null;
if (fileInformation == null || fileInformation.length < 1
|| fileInformation[0] == null || fileInformation[0].first == null) {
AssetManager assetManager = getAssets();
try {
istr=new URL("https://s18.postimg.org/rnoymr5o9/andes.jpg").openStream();
//istr = assetManager.open("andes.jpg");
panoOptions = new Options();
panoOptions.inputType = Options.TYPE_STEREO_OVER_UNDER;
} catch (IOException e) {
Log.e(TAG, "Could not decode default bitmap: " + e);
return false;
}
} else {
try {
istr = new FileInputStream(new File(fileInformation[0].first.getPath()));
panoOptions = fileInformation[0].second;
} catch (IOException e) {
Log.e(TAG, "Could not load file: " + e);
return false;
}
}
try {
istr.close();
} catch (IOException e) {
Log.e(TAG, "Could not close input stream: " + e);
}
return true;
}
#Override
protected void onPostExecute(Boolean aBoolean) {
super.onPostExecute(aBoolean);
panoWidgetView.loadImageFromBitmap(BitmapFactory.decodeStream(istr), panoOptions);
}
}
/**
* Listen to the important events from widget.
*/
private class ActivityEventListener extends VrPanoramaEventListener {
/**
* Called by pano widget on the UI thread when it's done loading the image.
*/
#Override
public void onLoadSuccess() {
loadImageSuccessful = true;
}
/**
* Called by pano widget on the UI thread on any asynchronous error.
*/
#Override
public void onLoadError(String errorMessage) {
loadImageSuccessful = false;
Toast.makeText(
SimpleVrPanoramaActivity.this, "Error loading pano: " + errorMessage, Toast.LENGTH_LONG)
.show();
Log.e(TAG, "Error loading pano: " + errorMessage);
}
}
}
You need to declare Sensors
private SensorManager mSensorManager;
private OrientationSensor mOrientationSensor;
mSensorManager = (SensorManager) this.getSystemService(SENSOR_SERVICE);
mOrientationSensor = new OrientationSensor(this, mSensorManager, OrientationSensor.MODE_LOOK_THROUGH);
Make your activity or fragment implements SensorEventListener
Create vector to retreive rotation values
private float[] mHeadRotation = new float[2];
In onSensorChanged
#Override
public void onSensorChanged(SensorEvent sensorEvent) {
mUiPanoWidgetView.getHeadRotation(mHeadRotation);
updateReticule();
}
Do what you want depending on where you are on picture. Example:
private void updateReticule() {
if(mHeadRotation[1] > -20 && mHeadRotation[1] < 20 && mHeadRotation[0] > -15 && mHeadRotation[0] < 15){
showButton();
} else {
showReticule();
}
}
showButton() can show or hide an arrow (ImageButton) in the center of the screen. Then you set an OnClickListener on the arrow. If users click, then you can go to next picture.
Hope it will help you :)

Power saving mode causes no network connection when app returns from background

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);
}
}

Detect and capture barcode automatically when it is read by camera

I used this android-vision project to scan a barcode. When the camera detects the barcode, I am currently required to manually tap to capture it. But, I want to change the code a little bit so that it is captured automatically when the barcode is detected. How can I do this? This is my BarcodeCaptureActivity class:
package com.example.fs.barcodescanner;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.hardware.Camera;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.WindowId;
import android.widget.Toast;
import com.example.fs.barcodescanner.ui.camera.CameraSource;
import com.example.fs.barcodescanner.ui.camera.CameraSourcePreview;
import com.example.fs.barcodescanner.ui.camera.GraphicOverlay;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.common.api.CommonStatusCodes;
import com.google.android.gms.vision.MultiProcessor;
import com.google.android.gms.vision.barcode.Barcode;
import com.google.android.gms.vision.barcode.BarcodeDetector;
import java.io.IOException;
public final class BarcodeCaptureActivity extends AppCompatActivity {
private static final String TAG = "Barcode-reader";
// intent request code to handle updating play services if needed.
private static final int RC_HANDLE_GMS = 9001;
// permission request codes need to be < 256
private static final int RC_HANDLE_CAMERA_PERM = 2;
// constants used to pass extra data in the intent
public static final String AutoFocus = "AutoFocus";
public static final String UseFlash = "UseFlash";
public static final String BarcodeObject = "Barcode";
private CameraSource mCameraSource;
private CameraSourcePreview mPreview;
private GraphicOverlay<BarcodeGraphic> mGraphicOverlay;
// helper objects for detecting taps and pinches.
private ScaleGestureDetector scaleGestureDetector;
private GestureDetector gestureDetector;
private WindowId.FocusObserver fo;
/**
* Initializes the UI and creates the detector pipeline.
*/
#Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.activity_barcode_capture);
mPreview = (CameraSourcePreview) findViewById(R.id.preview);
mGraphicOverlay = (GraphicOverlay<BarcodeGraphic>) findViewById(R.id.graphicOverlay);
// read parameters from the intent used to launch the activity.
boolean autoFocus = getIntent().getBooleanExtra(AutoFocus, false);
boolean useFlash = getIntent().getBooleanExtra(UseFlash, false);
// Check for the camera permission before accessing the camera. If the
// permission is not granted yet, request permission.
int rc = ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA);
if (rc == PackageManager.PERMISSION_GRANTED) {
createCameraSource(autoFocus, useFlash);
} else {
requestCameraPermission();
}
gestureDetector = new GestureDetector(this, new CaptureGestureListener());
scaleGestureDetector = new ScaleGestureDetector(this, new ScaleListener());
Snackbar.make(mGraphicOverlay, "Tap to capture. Pinch/Stretch to zoom",
Snackbar.LENGTH_LONG)
.show();
}
/**
* Handles the requesting of the camera permission. This includes
* showing a "Snackbar" message of why the permission is needed then
* sending the request.
*/
private void requestCameraPermission() {
Log.w(TAG, "Camera permission is not granted. Requesting permission");
final String[] permissions = new String[]{Manifest.permission.CAMERA};
if (!ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.CAMERA)) {
ActivityCompat.requestPermissions(this, permissions, RC_HANDLE_CAMERA_PERM);
return;
}
final Activity thisActivity = this;
View.OnClickListener listener = new View.OnClickListener() {
#Override
public void onClick(View view) {
ActivityCompat.requestPermissions(thisActivity, permissions,
RC_HANDLE_CAMERA_PERM);
}
};
Snackbar.make(mGraphicOverlay, R.string.permission_camera_rationale,
Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.ok, listener)
.show();
}
#Override
public boolean onTouchEvent(MotionEvent e) {
boolean b = scaleGestureDetector.onTouchEvent(e);
System.out.println("scale gesture"+b);
boolean c = gestureDetector.onTouchEvent(e);
System.out.println("tap gesture"+c);
return b || c || super.onTouchEvent(e);
}
/**
* Creates and starts the camera. Note that this uses a higher resolution in comparison
* to other detection examples to enable the barcode detector to detect small barcodes
* at long distances.
*
* Suppressing InlinedApi since there is a check that the minimum version is met before using
* the constant.
*/
#SuppressLint("InlinedApi")
private void createCameraSource(boolean autoFocus, boolean useFlash) {
Context context = getApplicationContext();
// A barcode detector is created to track barcodes. An associated multi-processor instance
// is set to receive the barcode detection results, track the barcodes, and maintain
// graphics for each barcode on screen. The factory is used by the multi-processor to
// create a separate tracker instance for each barcode.
BarcodeDetector barcodeDetector = new BarcodeDetector.Builder(context).build();
BarcodeTrackerFactory barcodeFactory = new BarcodeTrackerFactory(mGraphicOverlay);
barcodeDetector.setProcessor(
new MultiProcessor.Builder<>(barcodeFactory).build());
if (!barcodeDetector.isOperational()) {
// Note: The first time that an app using the barcode or face API is installed on a
// device, GMS will download a native libraries to the device in order to do detection.
// Usually this completes before the app is run for the first time. But if that
// download has not yet completed, then the above call will not detect any barcodes
// and/or faces.
//
// isOperational() can be used to check if the required native libraries are currently
// available. The detectors will automatically become operational once the library
// downloads complete on device.
Log.w(TAG, "Detector dependencies are not yet available.");
// Check for low storage. If there is low storage, the native library will not be
// downloaded, so detection will not become operational.
IntentFilter lowstorageFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
boolean hasLowStorage = registerReceiver(null, lowstorageFilter) != null;
if (hasLowStorage) {
Toast.makeText(this, R.string.low_storage_error, Toast.LENGTH_LONG).show();
Log.w(TAG, getString(R.string.low_storage_error));
}
}
// Creates and starts the camera. Note that this uses a higher resolution in comparison
// to other detection examples to enable the barcode detector to detect small barcodes
// at long distances.
CameraSource.Builder builder = new CameraSource.Builder(getApplicationContext(), barcodeDetector)
.setFacing(CameraSource.CAMERA_FACING_BACK)
.setRequestedPreviewSize(1600, 1024)
.setRequestedFps(15.0f);
// make sure that auto focus is an available option
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
builder = builder.setFocusMode(
autoFocus ? Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE : null);
}
mCameraSource = builder
.setFlashMode(useFlash ? Camera.Parameters.FLASH_MODE_TORCH : null)
.build();
}
/**
* Restarts the camera.
*/
#Override
protected void onResume() {
super.onResume();
startCameraSource();
}
/**
* Stops the camera.
*/
#Override
protected void onPause() {
super.onPause();
if (mPreview != null) {
mPreview.stop();
}
}
/**
* Releases the resources associated with the camera source, the associated detectors, and the
* rest of the processing pipeline.
*/
#Override
protected void onDestroy() {
super.onDestroy();
if (mPreview != null) {
mPreview.release();
}
}
/**
* Callback for the result from requesting permissions. This method
* is invoked for every call on {#link #requestPermissions(String[], int)}.
* <p>
* <strong>Note:</strong> It is possible that the permissions request interaction
* with the user is interrupted. In this case you will receive empty permissions
* and results arrays which should be treated as a cancellation.
* </p>
*
* #param requestCode The request code passed in {#link #requestPermissions(String[], int)}.
* #param permissions The requested permissions. Never null.
* #param grantResults The grant results for the corresponding permissions
* which is either {#link PackageManager#PERMISSION_GRANTED}
* or {#link PackageManager#PERMISSION_DENIED}. Never null.
* #see #requestPermissions(String[], int)
*/
#Override
public void onRequestPermissionsResult(int requestCode,
#NonNull String[] permissions,
#NonNull int[] grantResults) {
if (requestCode != RC_HANDLE_CAMERA_PERM) {
Log.d(TAG, "Got unexpected permission result: " + requestCode);
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
return;
}
if (grantResults.length != 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "Camera permission granted - initialize the camera source");
// we have permission, so create the camerasource
boolean autoFocus = getIntent().getBooleanExtra(AutoFocus,false);
boolean useFlash = getIntent().getBooleanExtra(UseFlash, false);
createCameraSource(autoFocus, useFlash);
return;
}
Log.e(TAG, "Permission not granted: results len = " + grantResults.length +
" Result code = " + (grantResults.length > 0 ? grantResults[0] : "(empty)"));
DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
finish();
}
};
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Multitracker sample")
.setMessage(R.string.no_camera_permission)
.setPositiveButton(R.string.ok, listener)
.show();
}
/**
* Starts or restarts the camera source, if it exists. If the camera source doesn't exist yet
* (e.g., because onResume was called before the camera source was created), this will be called
* again when the camera source is created.
*/
private void startCameraSource() throws SecurityException {
// check that the device has play services available.
int code = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(
getApplicationContext());
if (code != ConnectionResult.SUCCESS) {
Dialog dlg =
GoogleApiAvailability.getInstance().getErrorDialog(this, code, RC_HANDLE_GMS);
dlg.show();
}
if (mCameraSource != null) {
try {
mPreview.start(mCameraSource, mGraphicOverlay);
} catch (IOException e) {
Log.e(TAG, "Unable to start camera source.", e);
mCameraSource.release();
mCameraSource = null;
}
}
}
/**
* onTap is called to capture the oldest barcode currently detected and
* return it to the caller.
*
* #param rawX - the raw position of the tap
* #param rawY - the raw position of the tap.
* #return true if the activity is ending.
*/
private boolean onTap(float rawX, float rawY) {
//TODO: use the tap position to select the barcode.
BarcodeGraphic graphic = mGraphicOverlay.getFirstGraphic();
Barcode barcode = null;
if (graphic != null) {
barcode = graphic.getBarcode();
if (barcode != null) {
Intent data = new Intent();
data.putExtra(BarcodeObject, barcode);
setResult(CommonStatusCodes.SUCCESS, data);
finish();
}
else {
Log.d(TAG, "barcode data is null");
}
}
else {
Log.d(TAG,"no barcode detected");
}
return barcode != null;
}
private class CaptureGestureListener extends GestureDetector.SimpleOnGestureListener {
#Override
public boolean onSingleTapConfirmed(MotionEvent e) {
System.out.println(e.getRawX());
System.out.println(e.getRawY());
return onTap(e.getRawX(), e.getRawY()) || super.onSingleTapConfirmed(e);
}
}
private class ScaleListener implements ScaleGestureDetector.OnScaleGestureListener {
/**
* Responds to scaling events for a gesture in progress.
* Reported by pointer motion.
*
* #param detector The detector reporting the event - use this to
* retrieve extended info about event state.
* #return Whether or not the detector should consider this event
* as handled. If an event was not handled, the detector
* will continue to accumulate movement until an event is
* handled. This can be useful if an application, for example,
* only wants to update scaling factors if the change is
* greater than 0.01.
*/
#Override
public boolean onScale(ScaleGestureDetector detector) {
return false;
}
/**
* Responds to the beginning of a scaling gesture. Reported by
* new pointers going down.
*
* #param detector The detector reporting the event - use this to
* retrieve extended info about event state.
* #return Whether or not the detector should continue recognizing
* this gesture. For example, if a gesture is beginning
* with a focal point outside of a region where it makes
* sense, onScaleBegin() may return false to ignore the
* rest of the gesture.
*/
#Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
}
/**
* Responds to the end of a scale gesture. Reported by existing
* pointers going up.
* <p/>
* Once a scale has ended, {#link ScaleGestureDetector#getFocusX()}
* and {#link ScaleGestureDetector#getFocusY()} will return focal point
* of the pointers remaining on the screen.
*
* #param detector The detector reporting the event - use this to
* retrieve extended info about event state.
*/
#Override
public void onScaleEnd(ScaleGestureDetector detector) {
mCameraSource.doZoom(detector.getScaleFactor());
}
}
}
Use interfaces.
Steps to do:
Create interface, like this:
public interface QRCodeDetectedInterface {
void onQRCodeDetected();}
Implement it in BarcodeCaptureActivity, like this :
public final class BarcodeCaptureActivity extends AppCompatActivity implements QRCodeDetectedInterface{
#Override
public void onQRCodeDetected() {
BarcodeGraphic graphic = mGraphicOverlay.getFirstGraphic();
Barcode barcode = null;
if (graphic != null) {
barcode = graphic.getBarcode();
if (barcode != null) {
Intent data = new Intent();
data.putExtra(BarcodeObject, barcode);
setResult(CommonStatusCodes.SUCCESS, data);
finish();
}
else {
Log.d(TAG, "barcode data is null");
}
}
else {
Log.d(TAG,"no barcode detected");
}
}
}
And use it in BarcodeGraphic.java file, like this:
private Context context;
private QRCodeDetectedInterface mCallback;
BarcodeGraphic(GraphicOverlay overlay) {
super(overlay);
this.context = overlay.getContext();
mCallback = (BarcodeCaptureActivity) context;
}
And then:
#Override
public void draw(Canvas canvas) {
mCallback.onQRCodeDetected();}
As #Lynx said, you need to create an interface. I was in need of something like that. I created an interface to listen when the camera tracks the barcode and implemented that in the activity that will listen.
In BarcodeGraphicTracker.java class, This is the interface that I created.
public interface BarcodeDetectorListener{
//event call back
void onObjectDetected(Barcode data);
}
#Override
public void onNewItem(int id, Barcode item) {
mGraphic.setId(id);
if(mBarcodeDetectorListener == null) return;
mBarcodeDetectorListener.onObjectDetected(item);
mBarcodeDetectorListener = null;
}
in BarcodeScannerActivty
#Override
public void onObjectDetected(Barcode data) {
//do something with the barcode data here
Intent mIntent = new Intent();
mIntent.putExtra(BarcodeObject, data);
setResult(CommonStatusCodes.SUCCESS, mIntent);
finish();
}
I have uploaded a sample in github. Please find it here.
package com.example.fs.barcodescanner;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.hardware.Camera;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.widget.Toast;
import com.example.fs.barcodescanner.ui.camera.CameraSource;
import com.example.fs.barcodescanner.ui.camera.CameraSourcePreview;
import com.example.fs.barcodescanner.ui.camera.GraphicOverlay;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.common.api.CommonStatusCodes;
import com.google.android.gms.vision.MultiProcessor;
import com.google.android.gms.vision.barcode.Barcode;
import com.google.android.gms.vision.barcode.BarcodeDetector;
import java.io.IOException;
public final class BarcodeCaptureActivity extends AppCompatActivity {
private static final String TAG = "Barcode-reader";
// intent request code to handle updating play services if needed.
private static final int RC_HANDLE_GMS = 9001;
// permission request codes need to be < 256
private static final int RC_HANDLE_CAMERA_PERM = 2;
// constants used to pass extra data in the intent
public static final String AutoFocus = "AutoFocus";
public static final String UseFlash = "UseFlash";
public static final String BarcodeObject = "Barcode";
private CameraSource mCameraSource;
private CameraSourcePreview mPreview;
private GraphicOverlay<BarcodeGraphic> mGraphicOverlay;
// helper objects for detecting taps and pinches.
private ScaleGestureDetector scaleGestureDetector;
private GestureDetector gestureDetector;
/**
* Initializes the UI and creates the detector pipeline.
*/
#Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.activity_barcode_capture);
mPreview = (CameraSourcePreview) findViewById(R.id.preview);
mGraphicOverlay = (GraphicOverlay<BarcodeGraphic>) findViewById(R.id.graphicOverlay);
// read parameters from the intent used to launch the activity.
boolean autoFocus = getIntent().getBooleanExtra(AutoFocus, false);
boolean useFlash = getIntent().getBooleanExtra(UseFlash, false);
// Check for the camera permission before accessing the camera. If the
// permission is not granted yet, request permission.
int rc = ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA);
if (rc == PackageManager.PERMISSION_GRANTED) {
createCameraSource();
} else {
requestCameraPermission();
}
gestureDetector = new GestureDetector(this, new CaptureGestureListener());
scaleGestureDetector = new ScaleGestureDetector(this, new ScaleListener());
Snackbar.make(mGraphicOverlay, "Tap to capture. Pinch/Stretch to zoom",
Snackbar.LENGTH_LONG)
.show();
}
/**
* Handles the requesting of the camera permission. This includes
* showing a "Snackbar" message of why the permission is needed then
* sending the request.
*/
private void requestCameraPermission() {
Log.w(TAG, "Camera permission is not granted. Requesting permission");
final String[] permissions = new String[]{Manifest.permission.CAMERA};
if (!ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.CAMERA)) {
ActivityCompat.requestPermissions(this, permissions, RC_HANDLE_CAMERA_PERM);
return;
}
final Activity thisActivity = this;
View.OnClickListener listener = new View.OnClickListener() {
#Override
public void onClick(View view) {
ActivityCompat.requestPermissions(thisActivity, permissions,
RC_HANDLE_CAMERA_PERM);
}
};
Snackbar.make(mGraphicOverlay, R.string.permission_camera_rationale,
Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.ok, listener)
.show();
}
#Override
public boolean onTouchEvent(MotionEvent e) {
boolean b = scaleGestureDetector.onTouchEvent(e);
System.out.println("scale gesture" + b);
boolean c = gestureDetector.onTouchEvent(e);
System.out.println("tap gesture" + c);
return b || c || super.onTouchEvent(e);
}
/**
* Creates and starts the camera. Note that this uses a higher resolution in comparison
* to other detection examples to enable the barcode detector to detect small barcodes
* at long distances.
* <p>
* Suppressing InlinedApi since there is a check that the minimum version is met before using
* the constant.
*/
#SuppressLint("InlinedApi")
private void createCameraSource() {
Context context = getApplicationContext();
// A barcode detector is created to track barcodes. An associated multi-processor instance
// is set to receive the barcode detection results, track the barcodes, and maintain
// graphics for each barcode on screen. The factory is used by the multi-processor to
// create a separate tracker instance for each barcode.
BarcodeDetector barcodeDetector = new BarcodeDetector.Builder(context).build();
BarcodeTrackerFactory barcodeFactory = new BarcodeTrackerFactory(mGraphicOverlay);
barcodeDetector.setProcessor(
new MultiProcessor.Builder<>(barcodeFactory).build());
if (!barcodeDetector.isOperational()) {
// Note: The first time that an app using the barcode or face API is installed on a
// device, GMS will download a native libraries to the device in order to do detection.
// Usually this completes before the app is run for the first time. But if that
// download has not yet completed, then the above call will not detect any barcodes
// and/or faces.
//
// isOperational() can be used to check if the required native libraries are currently
// available. The detectors will automatically become operational once the library
// downloads complete on device.
Log.w(TAG, "Detector dependencies are not yet available.");
// Check for low storage. If there is low storage, the native library will not be
// downloaded, so detection will not become operational.
IntentFilter lowstorageFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
boolean hasLowStorage = registerReceiver(null, lowstorageFilter) != null;
if (hasLowStorage) {
Toast.makeText(this, R.string.low_storage_error, Toast.LENGTH_LONG).show();
Log.w(TAG, getString(R.string.low_storage_error));
}
}
// Creates and starts the camera. Note that this uses a higher resolution in comparison
// to other detection examples to enable the barcode detector to detect small barcodes
// at long distances.
CameraSource.Builder builder = new CameraSource.Builder(getApplicationContext(), barcodeDetector)
.setFacing(CameraSource.CAMERA_FACING_BACK)
.setRequestedPreviewSize(1600, 1024)
.setRequestedFps(15.0f)
.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
// make sure that auto focus is an available option
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
// builder = builder.setFocusMode(
// autoFocus ? Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE : null);
// }
mCameraSource = builder
//.setFlashMode(useFlash ? Camera.Parameters.FLASH_MODE_TORCH : null)
.build();
mCameraSource.autoFocus(new CameraSource.AutoFocusCallback() {
#Override
public void onAutoFocus(boolean success) {
System.out.println("Hello");
}
});
}
/**
* Restarts the camera.
*/
#Override
protected void onResume() {
super.onResume();
startCameraSource();
}
/**
* Stops the camera.
*/
#Override
protected void onPause() {
super.onPause();
if (mPreview != null) {
mPreview.stop();
}
}
/**
* Releases the resources associated with the camera source, the associated detectors, and the
* rest of the processing pipeline.
*/
#Override
protected void onDestroy() {
super.onDestroy();
if (mPreview != null) {
mPreview.release();
}
}
/**
* Callback for the result from requesting permissions. This method
* is invoked for every call on {#link #requestPermissions(String[], int)}.
* <p>
* <strong>Note:</strong> It is possible that the permissions request interaction
* with the user is interrupted. In this case you will receive empty permissions
* and results arrays which should be treated as a cancellation.
* </p>
*
* #param requestCode The request code passed in {#link #requestPermissions(String[], int)}.
* #param permissions The requested permissions. Never null.
* #param grantResults The grant results for the corresponding permissions
* which is either {#link PackageManager#PERMISSION_GRANTED}
* or {#link PackageManager#PERMISSION_DENIED}. Never null.
* #see #requestPermissions(String[], int)
*/
#Override
public void onRequestPermissionsResult(int requestCode,
#NonNull String[] permissions,
#NonNull int[] grantResults) {
if (requestCode != RC_HANDLE_CAMERA_PERM) {
Log.d(TAG, "Got unexpected permission result: " + requestCode);
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
return;
}
if (grantResults.length != 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "Camera permission granted - initialize the camera source");
// we have permission, so create the camerasource
boolean autoFocus = getIntent().getBooleanExtra(AutoFocus,false);
boolean useFlash = getIntent().getBooleanExtra(UseFlash, false);
createCameraSource();
return;
}
Log.e(TAG, "Permission not granted: results len = " + grantResults.length +
" Result code = " + (grantResults.length > 0 ? grantResults[0] : "(empty)"));
DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
finish();
}
};
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Multitracker sample")
.setMessage(R.string.no_camera_permission)
.setPositiveButton(R.string.ok, listener)
.show();
}
/**
* Starts or restarts the camera source, if it exists. If the camera source doesn't exist yet
* (e.g., because onResume was called before the camera source was created), this will be called
* again when the camera source is created.
*/
private void startCameraSource() throws SecurityException {
// check that the device has play services available.
int code = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(
getApplicationContext());
if (code != ConnectionResult.SUCCESS) {
Dialog dlg =
GoogleApiAvailability.getInstance().getErrorDialog(this, code, RC_HANDLE_GMS);
dlg.show();
}
if (mCameraSource != null) {
try {
mPreview.start(mCameraSource, mGraphicOverlay);
} catch (IOException e) {
Log.e(TAG, "Unable to start camera source.", e);
mCameraSource.release();
mCameraSource = null;
}
}
}
/**
* onTap is called to capture the oldest barcode currently detected and
* return it to the caller.
*
* #param rawX - the raw position of the tap
* #param rawY - the raw position of the tap.
* #return true if the activity is ending.
*/
private boolean onTap(float rawX, float rawY) {
//TODO: use the tap position to select the barcode.
BarcodeGraphic graphic = mGraphicOverlay.getFirstGraphic();
Barcode barcode = null;
if (graphic != null) {
barcode = graphic.getBarcode();
if (barcode != null) {
Intent data = new Intent();
data.putExtra(BarcodeObject, barcode);
setResult(CommonStatusCodes.SUCCESS, data);
finish();
} else {
Log.d(TAG, "barcode data is null");
}
} else {
Log.d(TAG, "no barcode detected");
}
return barcode != null;
}
private class CaptureGestureListener extends GestureDetector.SimpleOnGestureListener {
#Override
public boolean onSingleTapConfirmed(MotionEvent e) {
System.out.println(e.getRawX());
System.out.println(e.getRawY());
return onTap(e.getRawX(), e.getRawY()) || super.onSingleTapConfirmed(e);
}
}
private class ScaleListener implements ScaleGestureDetector.OnScaleGestureListener {
/**
* Responds to scaling events for a gesture in progress.
* Reported by pointer motion.
*
* #param detector The detector reporting the event - use this to
* retrieve extended info about event state.
* #return Whether or not the detector should consider this event
* as handled. If an event was not handled, the detector
* will continue to accumulate movement until an event is
* handled. This can be useful if an application, for example,
* only wants to update scaling factors if the change is
* greater than 0.01.
*/
#Override
public boolean onScale(ScaleGestureDetector detector) {
return false;
}
/**
* Responds to the beginning of a scaling gesture. Reported by
* new pointers going down.
*
* #param detector The detector reporting the event - use this to
* retrieve extended info about event state.
* #return Whether or not the detector should continue recognizing
* this gesture. For example, if a gesture is beginning
* with a focal point outside of a region where it makes
* sense, onScaleBegin() may return false to ignore the
* rest of the gesture.
*/
#Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
}
/**
* Responds to the end of a scale gesture. Reported by existing
* pointers going up.
* <p/>
* Once a scale has ended, {#link ScaleGestureDetector#getFocusX()}
* and {#link ScaleGestureDetector#getFocusY()} will return focal point
* of the pointers remaining on the screen.
*
* #param detector The detector reporting the event - use this to
* retrieve extended info about event state.
*/
#Override
public void onScaleEnd(ScaleGestureDetector detector) {
mCameraSource.doZoom(detector.getScaleFactor());
}
}
}

How to use ACR35 NFC Reader in Android

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

is it possible to quit looper in onReceive method in BroadcastReceiver

Using the following code and when onReceive is fired,am getting the following error
Error receiving broadcast Intent { act=com.sample.service.ReminderActivityService flg=0x10 (has extras) }
in com.sample.common.UserActivity$1#41c2b4b0
The problem is this statement Looper.myLooper().quit();
How do I terminate my looper after receiving the broadcast in the code below?
public class UserActivity extends Thread implements
ConnectionCallbacks, OnConnectionFailedListener {
private String TAG;
// Constants that define the activity detection interval
public static final int MILLISECONDS_PER_SECOND = 1000;
public static final int DETECTION_INTERVAL_SECONDS = 30;
public static final int DETECTION_INTERVAL_MILLISECONDS = MILLISECONDS_PER_SECOND * DETECTION_INTERVAL_SECONDS;
IntentService is;
onActivityGot mCallback;
Handler mHandler;
Context mContext;
BroadcastReceiver br;
/*
* Store the PendingIntent used to send activity recognition events
* back to the app
*/
private PendingIntent mActivityRecognitionPendingIntent;
// Store the current activity recognition client
private ActivityRecognitionClient mActivityRecognitionClient;
public UserActivity(UserActivity.onActivityGot ints) {
is = (IntentService) ints;
mContext = is.getApplicationContext();
mHandler = new Handler();
TAG = this.getClass().getSimpleName();
// This makes sure that the container service has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (UserActivity.onActivityGot) ints;
} catch (ClassCastException e) {
throw new ClassCastException(ints.toString()
+ " must implement UserActivity.onActivityGot");
}
Log.i(TAG, "UserActivity constractor fired in activity");
}
#Override
public void run() {
if (servicesConnected()) {
Looper.prepare();
Log.i(TAG, "servicesConnected fired in activity");
/*
* Instantiate a new activity recognition client. Since the
* parent Activity implements the connection listener and
* connection failure listener, the constructor uses "this"
* to specify the values of those parameters.
*/
mActivityRecognitionClient =
new ActivityRecognitionClient(mContext, this, this);
// connect to the service
mActivityRecognitionClient.connect();
br = new BroadcastReceiver() {
#Override
public void onReceive(Context c, Intent i) {
//call calback with data
mCallback.activityKnown(i);
mActivityRecognitionClient.removeActivityUpdates(mActivityRecognitionPendingIntent);
mActivityRecognitionClient.disconnect();
mContext.unregisterReceiver(br);
Looper.myLooper().quit();
}
};
mContext.registerReceiver(br, new IntentFilter("com.sample.service.ReminderActivityService"));
Looper.loop();
}
}
#Override
public void onConnected(Bundle dataBundle) {
Log.i(TAG, "onConnected fired");
/*
* Create the PendingIntent that Location Services uses
* to send activity recognition updates back to this app.
*/
Intent intent = new Intent(
mContext, ReminderActivityService.class);
/*
* Return a PendingIntent that starts the IntentService.
*/
mActivityRecognitionPendingIntent =
PendingIntent.getService(mContext, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
/*
* Request activity recognition updates using the preset
* detection interval and PendingIntent. This call is
* synchronous.
*/
mActivityRecognitionClient.requestActivityUpdates(
DETECTION_INTERVAL_MILLISECONDS,
mActivityRecognitionPendingIntent);
}
#Override
public void onDisconnected() {
// Delete the client
mActivityRecognitionClient = null;
Looper.myLooper().quit();
Log.i(TAG, "onDisconnected fired");
}
#Override
public void onConnectionFailed(ConnectionResult cr) {
mHandler.post(new UiToastCommunicaton(mContext,
is.getResources().getString(R.string.action_connfailed)));
mCallback.activityFail();
Looper.myLooper().quit();
Log.i(TAG, "onConnectionFailed fired");
}
private boolean servicesConnected() {
// Check that Google Play services is available
int resultCode =
GooglePlayServicesUtil.
isGooglePlayServicesAvailable(is.getBaseContext());
if (ConnectionResult.SUCCESS == resultCode) {// If Google Play services is available
// In debug mode, log the status
Log.d("Activity Recognition",
"Google Play services is available.");
// Continue
return true;
} else {// Google Play services was not available for some reason
mHandler.post(new UiToastCommunicaton(mContext,
is.getResources().getString(R.string.gpserv_notfound)));
return false;
}
}
public interface onActivityGot {
public void activityKnown(Intent i);
public void activityFail();
}
}
found a way by storing a handle to the looper in a static variable. view below.
declare the variable
public static Handler looperHandle;
set the variable after preparing looper
Looper.prepare();
looperHandle = new Handler();
since i had instantiated the class in an object i just called
object.looperHandle.getLooper().quit();
am not comfortable with this solution because of using a static variable.
if someone has a better solution please post it here.

Categories

Resources