I'm working on an Android app (Java; minSdkVersion 21) and using One Signal to receive notifications. The One Signal notification will send a payload with enough data to build an Episode object which the setNotificationOpened handler will use to navigate to a particular episode in the app.
I'm doing this in MainActivity (the app has this one activity, everything else is fragments):
#Override
protected void onCreate(Bundle savedInstanceState) {
...
OneSignal.setNotificationOpenedHandler(
new OneSignal.OSNotificationOpenedHandler() {
#Override
public void notificationOpened(OSNotificationOpenedResult result) {
OSNotification notification = result.getNotification();
try {
String notificationType = (String) notification.getAdditionalData().get("type");
if (notificationType.equals("episode")) {
Episode episode = NotificationHandler.setUpEpisode(notification.getAdditionalData()); // returns an episode object
Bundle bundle = new Bundle();
bundle.putSerializable("episode", episode);
bundle.putString("title", episode.getName());
navController.navigate(R.id.action_global_episodeFragment, bundle);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
});
...
This setup works fine when the app is in the background. The user clicks on the notification, the app comes to the foreground and navigates to the appropriate episode. However, when the app is closed and the user clicks on the notification, the app is never launched.
I initially tried to put this handler in the AppController instead of MainActivity but couldn't find a way of navigating since R was not available at this point.
How can I get the above code to also work in cases where the app is closed?
}
Related
I'm working on an NFC based app developed in Xamarin for Android.
The app has a 'Connect' button and the NFC scanning process starts only when that button
is tapped.
With proper intents configured, whenever an NFC tag is detected, HandleNewIntent() method gets called and NFC read procedure is followed.
internal void HandleNewIntent(Intent intent)
{
try
{
if (intent.Action == NfcAdapter.ActionTagDiscovered || intent.Action == NfcAdapter.ActionNdefDiscovered)
{
_currentTag = intent.GetParcelableExtra(NfcAdapter.ExtraTag) as Tag;
if (_currentTag != null)
{
Task.Run(() => { ReadDataFromTag(_currentTag); });
}
}
}
catch (Exception ex)
{
//Display error
}
}
In normal cases this works fine. However, if the phone is kept in contact with the NFC tag and then the 'Connect' button is tapped on, then the TagDiscovered intent never gets fired. User has to take the phone away, bring it back in contact with the NFC tag and then only the event gets fired.
I observed the same behaviour with generic NFC apps on play store, and on 2 different Android phones.
Looks like Android keeps the NFC of phone tied up when in contact with NFC tag because of which the intents are not detected. Is there anything to be done to release such links (if any) before initiating new NFC connection?
All NFC is handled by the System NFC service and System App and is normally triggered by a Tag coming in to range and then an Intent being delivered to the right app immediately, therefore the system NFC service has already delivered the Intent to another App before you App has had the button pressed.
You don't show all the code that you do to setup your Apps' NFC configuration but as you are talking about Intents you are probably using the old and unreliable enableForegroundDispatch API which has a number of issues.
While it is more normal to enable foreground NFC detection as soon as you App is resumed and then process the Tag on immediate detection, it is possible to store the Tag object until a button is pressed.
I suggest that you use the better and newer enableReaderMode API that has less of a problem because it is a more direct interface to the NFC hardware and thus you use case seems to work using the code below.
public class MainActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback{
private NfcAdapter mNfcAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void Button(View view) {
Log.v("TAG", "DetailedScoresActivity:onTagDiscovered:Start");
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
if(mNfcAdapter!= null) {
Bundle options = new Bundle();
options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 250);
mNfcAdapter.enableReaderMode(this,
this,
NfcAdapter.FLAG_READER_NFC_A |
NfcAdapter.FLAG_READER_NFC_B |
NfcAdapter.FLAG_READER_NFC_F |
NfcAdapter.FLAG_READER_NFC_V |
NfcAdapter.FLAG_READER_NFC_BARCODE |
NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK |
NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS,
options);
}
}
public void onTagDiscovered(Tag tag) {
Log.v("TAG", "onTagDiscovered:Start");
}
#Override
protected void onPause() {
super.onPause();
if(mNfcAdapter!= null)
mNfcAdapter.disableReaderMode(this);
}
}
For me you can place a Tag against the phone and then start the App and as soon as the Button is clicked and `enableReaderMode` is enabled then `onTagDiscovered` is triggered with the Tag object.
Using AppCenter I am able to send push notification to all my devices with my Xamarin Forms (android only) app.
Since my devices, are going to be shared, I can't do the filter of the notifications on the AppCenter side based on the devices IDs.
I need to make the filter based on the current logged in user to my application. For this with the push notification I also send the WorkerID, which serves as a filter.
While I'm able to do this filter when the app is in foreground, its not working when the app is in background or not running.(normal behaviour since the push event is in App Start)
protected override void OnStart()
{
// This should come before AppCenter.Start() is called
// Avoid duplicate event registration:
if (!AppCenter.Configured)
{
Push.PushNotificationReceived += (sender, e) =>
{
var title = e.Title;
var message = e.Message;
// If app is in background title and message are null
// App in foreground
if (!string.IsNullOrEmpty(title))
{
foreach (string key in e.CustomData.Keys)
{
if (e.CustomData[key] == Settings.WorkerID)
{
Current.MainPage.DisplayAlert(title, message, "OK");
}
}
}
};
}
// Handle when your app starts
AppCenter.Start("android=xxxxxxxxxxxxxxxxxx", typeof(Push));
}
Is there a way to intercept and filter the push notifications when the app is in background and block them when the app is not running (since no user is yet logged in) ?
To filter users when sending push notifications it's preferable to set user id:
AppCenter.SetUserId("your-user-id");
Then on the appcenter.ms go to Push > Send notificaiton. Fill in the required fields.
And then in the second step select User list instead of All registered devices. Then enter user ids, separated by commas.
Through the document, you can enable-or-disable-push-at-runtime, so can enable or disable push when use go to background or go back to foreground, like this:
protected override void OnSleep()
{
// Handle when your app sleeps
Push.SetEnabledAsync(false);
}
protected override void OnResume()
{
// Handle when your app resumes
Push.SetEnabledAsync(true);
}
You can also enable or disable push when user login or login out:
public void userLogin() {
Push.SetEnabledAsync(true);
}
public void userLoginOut() {
Push.SetEnabledAsync(false);
}
Set it in the right place to meet your requirement.
I'm trying to implement WeChat InApp payments in our app. But we are struggling to make it work.
I will try to sum it up real quick.
Given user is not logged in, WeChat login screen show up every time.
Given user is logged in, when clicked on pay button for a first time, WeChat order info screen shows up, but when clicked back, and clicked on pay button again (in our app), WeChat screen doesn’t show up.
We did implemented WXPayEntryActivity but neither onCreate, onNewIntent nor onResp are called. And yes, this activity is sending broadcast but neither toast nor log shows up.
I tried call registerApp on application started, I tried it just before creating payment req.
Did anybody come across this issue?
Can WeChat help me directly?
Want to see some code?
This is my payment class
public class WXInAppPayment {
public void startPayment(AppCompatActivity activity, PaymentDataResponse data) {
IWXAPI api = getApi(activity);
if (api.isWXAppInstalled()) {
api.sendReq(getPayRequest(data));
} else {
// Showing toast
}
}
public WXReceiver getReceiver() {
// returning BR for wechat payments
return new WXReceiver();
}
public IntentFilter getIntentFilter() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Constants.WE_CHAT_BR_ID);
return intentFilter;
}
private IWXAPI getApi(AppCompatActivity activity) {
final IWXAPI api = WXAPIFactory.createWXAPI(activity, null);
api.registerApp(Constants.WE_CHAT_APP_ID);
return api;
}
private PayReq getPayRequest(PaymentDataResponse data) {
PayReq request = new PayReq();
request.appId = dataFromAPI.appId;
request.partnerId = dataFromAPI.partnerId;
request.prepayId = dataFromAPI.prepayId;
request.packageValue = dataFromAPI.packageValue;
request.nonceStr = dataFromAPI.nonceStr;
request.timeStamp = dataFromAPI.timestimeStampamp;
request.sign = dataFromAPI.sign;
return request;
}
}
And this is WXPayEntryActivity. In manifest:
<activity android:name=".wxapi.WXPayEntryActivity"
android:label="#string/app_name"
android:exported="true"/>
And class:
public class WXPayEntryActivity extends Activity implements IWXAPIEventHandler {
private final String TAG = getClass().getSimpleName();
private IWXAPI api;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
api = WXAPIFactory.createWXAPI(this, Constants.WE_CHAT_APP_ID);
api.handleIntent(getIntent(), this);
}
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
api.handleIntent(intent, this);
}
#Override
public void onReq(BaseReq baseReq) {
Log.e(TAG, "onReq: " + baseReq.transaction);
}
#Override
public void onResp(BaseResp baseResp) {
Log.e(TAG, "onResp: " + baseResp.errStr + " " + baseResp.errCode);
Intent intent = new Intent(Constants.WE_CHAT_BR_ID);
intent.putExtra("error_code", baseResp.errCode);
intent.putExtra("error_string", baseResp.errStr);
sendBroadcast(intent);
finish();
}
}
I went through same issue... Your code look fine.
lets cover the scenario:
This is normal ... if user is not logged in.. Wechat App will
redirect to login screen
"Only first time payment passed" happened due to wrong packageName. consider these checks:
You need to use ApplicationId not packageName
WhiteSpace
Debug buildType by default has suffix: .debug to applicatonId
Check AppSign which is MD5 of cert you sign with.. Be careful not to use the default one for debug buildType.
Try to reassign ApplicationId and AppSign it again.(that was our issue 😞) due to hidden WS not visible.
Contact Wechat team support.. they have logs to payment.
I have inAppBilling working but i'm trying to refine it a little bit. I noticed that when I try in app billing in Angry Birds it opens directly on top of the application but my application pulls in app billing to the foreground/desktop. I read the docs and it states that singleTop must be off and reflection must be used as well with an activity context given and NOT an application context. I have verified that I have all of those things done but its still pulling to the foreground. Any ideas?
My verification:
I have an activity called MyActivity
Log.d("TEST", mContext.getClass().getName());
responds back with MyActivity
and in Android Manifest for MyActivity
android:launchMode="standard"
EDIT:
The code that starts the checkout activity
void startBuyPageActivity(PendingIntent pendingIntent, Intent intent) {
if (mStartIntentSender != null) {
// This is on Android 2.0 and beyond. The in-app buy page activity
// must be on the activity stack of the application.
try {
// This implements the method call:
// mActivity.startIntentSender(pendingIntent.getIntentSender(),
// intent, 0, 0, 0);
mStartIntentSenderArgs[0] = pendingIntent.getIntentSender();
mStartIntentSenderArgs[1] = intent;
mStartIntentSenderArgs[2] = Integer.valueOf(0);
mStartIntentSenderArgs[3] = Integer.valueOf(0);
mStartIntentSenderArgs[4] = Integer.valueOf(0);
mStartIntentSender.invoke(mActivity, mStartIntentSenderArgs);
Log.d("TAG", mActivity.getClass().getName());
} catch (Exception e) {
Log.e(TAG, "error starting activity", e);
}
} else {
// This is on Android version 1.6. The in-app buy page activity must be on its
// own separate activity stack instead of on the activity stack of
// the application.
try {
pendingIntent.send(mActivity, 0 /* code */, intent);
} catch (CanceledException e) {
Log.e(TAG, "error starting activity", e);
}
}
}
What I am trying to accomplish is for the in app billing prompt whether it finishes the transaction or cancels to resume back to my game instead of simply closing and finishing. A current fix I have is to start the games Activity again which would result in reloading everything and putting the player back at the main menu page.
I want to extend a common security check to nearly every view of my application. To do this, I have made this class
public class ProtectedActivity extends ActivityBase {
boolean isAuthenticated = false;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Thread validationThread = new Thread()
{
#Override
public void run()
{
try
{
isAuthenticated = UserService.validateToken();
}
catch (FTNIServiceException e)
{
//eat it
}
finally
{
if (!isAuthenticated)
{
startActivity(new Intent(ProtectedActivity.this, SignInActivity.class));
finish();
}
}
}
};
validationThread.start();
}
}
The logic is simple. Validate the user against my restful api to make sure they are signed in. If they aren't, show them to the signin page.
This works great, because to add the security check, all I need to do is inherit from my ProtectedActivity.
public class MainMenuActivity extends ProtectedActivity{
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
The problem is, however, that I periodically receive View not attached to window manager errors. I understand why this is happening. I am starting a new intent in the parent class, and the child lives on. to attempt to alter it's view even though a new intent has started. What is a better way to handle this so that if a user is not authenticated (such as their session expires serverside), it won't error when sending the user to the sign in screen?
Don't you Thread. Use AsyncTask instead which should handle your references to windows correctly.
On a different note, I would change this to a different implementation. Why don't use the Preferences storage on the phone to store some kind token. If the token is not valid then request a new token and all the stuff you are doing currently. This way is better because you don't want to request a REST call every time.
I imagine something like this (pseudo code)
Check if credentials exist in Preference
if(valid) then do nothing
else use AsyncTask and pop up a loader screen "Waiting..."