I was trying to use http://www.androidbootstrap.com/ to bootstrap a new Android application. It creates a project with Otto, Dagger, Butterknife, Retrofit, and some other nifty stuff while also creating sample code on how to use it. It's really useful, as it sets up the annotation processing and everything in the Gradle build files for Android Studio to import it easily, it's really neat.
However, I'm at a loss with the login screen.
/**
* This method gets called when the
* methods gets invoked.
* This happens on a different process, so debugging it can be a beast.
*
* #param response
* #param account
* #param authTokenType
* #param options
* #return
* #throws NetworkErrorException
*/
#Override
public Bundle getAuthToken(final AccountAuthenticatorResponse response,
final Account account, final String authTokenType,
final Bundle options) throws NetworkErrorException {
Ln.d("Attempting to get authToken");
final String authToken = AccountManager.get(context).peekAuthToken(account, authTokenType);
final Bundle bundle = new Bundle();
bundle.putString(KEY_ACCOUNT_NAME, account.name);
bundle.putString(KEY_ACCOUNT_TYPE, Constants.Auth.BOOTSTRAP_ACCOUNT_TYPE);
bundle.putString(KEY_AUTHTOKEN, authToken);
return bundle;
}
#Override
public String getAuthTokenLabel(final String authTokenType) {
return authTokenType.equals(Constants.Auth.AUTHTOKEN_TYPE) ? authTokenType : null;
}
#Override
public Bundle hasFeatures(final AccountAuthenticatorResponse response, final Account account,
final String[] features) throws NetworkErrorException {
final Bundle result = new Bundle();
result.putBoolean(KEY_BOOLEAN_RESULT, false);
return result;
}
#Override
public Bundle updateCredentials(final AccountAuthenticatorResponse response,
final Account account, final String authTokenType,
final Bundle options) {
return null;
}
}
I cannot authenticate it and actually "log in".
So my questions are the following:
What is this authenticator authenticating against? The android device account, or Parse.com, or something completely different?
How exactly does this authenticator work? Is there a guide somewhere that explains how this should be done (note, on the AndroidBootstrap website, there isn't, and the video guide is outdated) if it weren't for the Bootstrap? To me this looks like a giant mess with random Services (like AccountAuthenticatorService), an AbstractAccountAuthenticator... and while I'm sure it's good for something, it looks needlessly overcomplicated, and I don't understand what is happening.
As written on https://github.com/AndroidBootstrap/android-bootstrap - the login credentials are
username: demo#androidbootstrap.com
password: android
It authenticates you against Parse.com as that is what it is set up against as a demo.
This is using an account authenticator which was added in Android v2.0, and adds the account to the Accounts in Android.
More information is available here:
http://udinic.wordpress.com/2013/04/24/write-your-own-android-authenticator/
http://www.jiahaoliuliu.com/2012/05/android-account-manager-part-i.html
http://www.c99.org/2010/01/23/writing-an-android-sync-provider-part-1/
Related
I followed the guide on the Android docs but for some reason nothing is showing when i start my app.
I even tried logging the listeners but nothing is showing up in logcat.
I also changed the ad technology in admob setting to Custom set of ad technology providers, but still not working.
My code
ConsentInformation consentInformation = ConsentInformation.getInstance(getApplicationContext());
ConsentInformation.getInstance(getApplicationContext()).addTestDevice("6AE7D8950FE9E464D988F340C0D625B0");
ConsentInformation.getInstance(getApplicationContext()).
setDebugGeography(DebugGeography.DEBUG_GEOGRAPHY_EEA);
String[] publisherIds = {""};
consentInformation.requestConsentInfoUpdate(publisherIds, new ConsentInfoUpdateListener() {
#Override
public void onConsentInfoUpdated(ConsentStatus consentStatus) {
// User's consent status successfully updated.
Log.d(TAG,"onConsentInfoUpdated");
}
#Override
public void onFailedToUpdateConsentInfo(String errorDescription) {
// User's consent status failed to update.
Log.d(TAG,"onFailedToUpdateConsentInfo");
}
});
form = new ConsentForm.Builder(this, privacyUrl)
.withListener(new ConsentFormListener() {
#Override
public void onConsentFormLoaded() {
// Consent form loaded successfully.
Log.d(TAG,"form loaded!");
form.show();
}
#Override
public void onConsentFormOpened() {
// Consent form was displayed.
}
#Override
public void onConsentFormClosed(
ConsentStatus consentStatus, Boolean userPrefersAdFree) {
// Consent form was closed.
}
#Override
public void onConsentFormError(String errorDescription) {
// Consent form error.
Log.d(TAG,"form error!");
}
})
.withPersonalizedAdsOption()
.withNonPersonalizedAdsOption()
.withAdFreeOption()
.build();
form.load();
Gradle
dependencies {
classpath 'com.google.gms:google-services:4.3.2'
}
implementation 'com.google.android.ads.consent:consent-library:1.0.7'
implementation 'com.google.android.gms:play-services-plus:17.0.0'
implementation 'com.google.android.gms:play-services-ads:18.2.0'
EDIT
I tried it on a project which was pre android x and now it calls the listener onFailedToUpdateConsentInfo.
With following error message:
onFailedToUpdateConsentInfoCould not parse Event FE preflight response.
Searched a bit and found this could be because of an invalid pub id, but i'm certain i'm using the right one.
1) I think you forget to check isRequestLocationInEeaOrUnknown() method.
It will return true If user already agreed to the consent. In this case, you don't need to ask it again. I think you already agreed to consent.
wrap your code with
if(ConsentInformation.getInstance(context).isRequestLocationInEeaOrUnknown()){
//setup admob
}else{
//Ask for consent
}
2) You have to call form.show(); to present the form to the user, check Google Doc
I was still using test app id and test ad ids, remove them and change it with your id's and make sure you use it as a testdevice so you don't violate admob policies.
Like this
adLoader.loadAd(new AdRequest.Builder().addTestDevice(AdRequest.DEVICE_ID_EMULATOR).build());
As we use firebase job dispatcher to run a job when firebase notification is received there was no other way to run code on the notification received that was old, now WorkManager is here.
It is working fine when the application is opened but when the application is closed it does not work, but firebase job dispatcher works fine, I want it to work using WorkManager API.
I tried in the following way it is 100% working code and doing the job using WorkManager but only when the application is opened, I want it to work when the application is not opened, I know that we register a service for the case of firebase job dispatcher in manifest but what to do for WorkManager -:)
I tried the ways that are related to job dispatcher but they didn't work e.g service in manifest etc...
public class BackgroundWorker extends ListenableWorker {
private static final ListenableFuture listenableFuture = null ;
/**
* #param appContext The application {#link Context}
* #param workerParams Parameters to setup the internal state of this worker
*/
public BackgroundWorker(#NonNull Context appContext, #NonNull WorkerParameters workerParams) {
super(appContext, workerParams);
}
#NonNull
#Override
public ListenableFuture<ListenableWorker.Result> startWork() {
// Do your work here.
// Return a ListenableFuture<>
return listenableFuture;
}
#Override
public void onStopped() {
// Cleanup because you are being stopped.
}
public void toast(String msg, Context applicationContext)
{
Toast.makeText(applicationContext,msg,Toast.LENGTH_LONG).show();
}
}
public class MyFirebaseMessagingService extends FirebaseMessagingService {
private static final String TAG = "MyFirebaseMsgService";
/**
* Called when message is received.
*
* #param remoteMessage Object representing the message received from Firebase Cloud Messaging.
*/
// [START receive_message]
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
sendNotification(remoteMessage.getData().get("body"),remoteMessage.getData().get("title"));
scheduleJob();
}
/**
* Called if InstanceID token is updated. This may occur if the security of
* the previous token had been compromised. Note that this is called when the InstanceID token
* is initially generated so this is where you would retrieve the token.
*/
/**
* Schedule a job using workmanager.
*/
private void scheduleJob() {
String unique_id = getRandomString(6);
Data inputData = new Data.Builder()
.putString("bulksmswebapi", unique_id)
.build();
// [START dispatch_job]
Constraints constraints = new Constraints.Builder()
// The Worker needs Network connectivity
.setRequiredNetworkType(NetworkType.CONNECTED)
// Needs the device to be charging
// .setRequiresCharging(true)
.build();
OneTimeWorkRequest workRequest =
// Tell which work to execute
new OneTimeWorkRequest.Builder(BackgroundWorker.class)
// Sets the input data for the ListenableWorker
.setInputData(inputData)
// If you want to delay the start of work by 60 seconds
.setInitialDelay(1, TimeUnit.SECONDS)
// Set a backoff criteria to be used when retry-ing
// .setBackoffCriteria(BackoffCriteria.EXPONENTIAL, 30000, TimeUnit.MILLISECONDS)
// Set additional constraints
.setConstraints(constraints)
.build();
WorkManager.getInstance()
// Use ExistingWorkPolicy.REPLACE to cancel and delete any existing pending
// (uncompleted) work with the same unique name. Then, insert the newly-specified
// work.
.enqueueUniqueWork(unique_id, ExistingWorkPolicy.KEEP, workRequest);
// [END dispatch_job]
}
}
Your worker code does nothing. You are returning null ListenableFuture, meaning WorkManager can't do anything. onStartWork is also explicitly marked as #NonNull so I'm not sure what you're doing there. You have a method that's never executed (toast) because you don't call it anywhere.
Problem is resolved by me, Answer to the question is Just make a Worker extended class and Don't use any Toast or other messages then it will work fine.
I have successfully implemented In-App-Billing into my app. The IAP is working successfully and I have tested it to make sure its working.
When a user clicks on a button, they have to make an IAP to proceed. However, everytime a user clicks on the button it starts the IAP, even though they have already made the IAP. I want my IAP to be non-consumable obviously. Currently, I'm storing the IAP in SharedPreferences, but if the user reinstalls the app, they lose their IAP.
So how can I use getPurchases() or restoreTransactions() on my onCreate or onClick method to check whether the user has purchases a specific item? I have searched all over the Internet and read through so many samples and it doesn't seem to work, perhaps I am misunderstanding though.
If you need me to post any code, please ask and I'll update my post.
Use this library:
https://github.com/anjlab/android-inapp-billing-v3
How to use?
Use this in your gradle:
repositories {
mavenCentral()
}
dependencies {
implementation 'com.anjlab.android.iab.v3:library:1.0.44'
}
Manifest permission for in-app billing:
<uses-permission android:name="com.android.vending.BILLING" />
How to use library method:
public class SomeActivity extends Activity implements BillingProcessor.IBillingHandler {
BillingProcessor bp;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bp = new BillingProcessor(this, "YOUR LICENSE KEY FROM GOOGLE PLAY CONSOLE HERE", this);
bp.initialize();
// or bp = BillingProcessor.newBillingProcessor(this, "YOUR LICENSE KEY FROM GOOGLE PLAY CONSOLE HERE", this);
// See below on why this is a useful alternative
}
// IBillingHandler implementation
#Override
public void onBillingInitialized() {
/*
* Called when BillingProcessor was initialized and it's ready to purchase
*/
}
#Override
public void onProductPurchased(String productId, TransactionDetails details) {
/*
* Called when requested PRODUCT ID was successfully purchased
*/
}
#Override
public void onBillingError(int errorCode, Throwable error) {
/*
* Called when some error occurred. See Constants class for more details
*
* Note - this includes handling the case where the user canceled the buy dialog:
* errorCode = Constants.BILLING_RESPONSE_RESULT_USER_CANCELED
*/
}
#Override
public void onPurchaseHistoryRestored() {
/*
* Called when purchase history was restored and the list of all owned PRODUCT ID's
* was loaded from Google Play
*/
}
}
Note: onPurchaseHistoryRestored called only first time when you initialize BillingProcessor
Their are lot of ways to check onPurchaseHistoryRestored() but its my opinion. And I have solved it by using..
you can check through transection detail using this code.
#Override
public void onPurchaseHistoryRestored() {
/*
* Called when purchase history was restored and the list of all owned PRODUCT ID's
* was loaded from Google Play
*/
// Check whether 'premium_id' has previously been purchased:
TransactionDetails premiumTransactionDetails = bp.getPurchaseTransactionDetails("premium_id");
if (premiumTransactionDetails == null) {
Log.i(TAG, "onPurchaseHistoryRestored(): Havn't bought premium yet.");
purchasePremiumButton.setEnabled(true);
}
else {
Log.i(TAG, "onPurchaseHistoryRestored(): Already purchases premium.");
purchasePremiumButton.setText(getString(R.string.you_have_premium));
purchasePremiumButton.setEnabled(false);
statusTextView.setVisibility(View.INVISIBLE);
}
}
And the second is to put the check on "product id".check below code.
if(bp.isPurchased(REMOVE_ID_SKU)){
if (bp.loadOwnedPurchasesFromGoogle()) {
Toast.makeText(this, R.string.subs_updated, Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(this, R.string.no_purchases, Toast.LENGTH_SHORT).show();
}
}
I use mikepenz's MaterialDrawer in my Android app. The app supports multiple logged in profiles at the same time, similar to Gmail. So while one profile is logged in, another profile can be added and then the user can switch the active profile by clicking on the icon in the header.
I use Volley to asynchronously load the profile picture from a server. This means, when I create the list of ProfileDrawerItems for the logged in profiles (when starting the app), I add a default icon. It should be updated as soon as the Volley request returns the correct icon. The same logic applies, when adding a new account.
This work partly. The correct icons are shown in the full account list (below the Account Header). But in the Account Header, I still see the default icons:
Example showing the default icon for the latest added profile. This picture shows, what the drawer looks like after adding a new profile ("Jubi").
After a successful login, I use
getAccountHeader().getProfiles().add(loadProfile(token));
to add the new profile to the Account Header. loadProfile(Token) looks like this:
/**
* Loads and returns the profile for the given token. If a profile already exists in the map profilesByUserId for the token's userID, that profile is updated, otherwise a new profile is created and put into that map.
* The returned profile might not yet be ready, when this method returns. Some date is loaded asynchronously: Picture, username, user's first and last name.
*
* #param token the token, for which the profile should be created or updated
* #return the profile
*/
public ProfileDrawerItem loadProfile(Token token) {
ProfileDrawerItem profile = profilesByUserId.get(token.getUserId());
if (profile == null) {
profile = new ProfileDrawerItem();
profilesByUserId.put(token.getUserId(), profile);
profile.withIdentifier(token.getId());
profile.withName(token.getUsername());
profile.withIcon(R.drawable.default_profile_pic);
}
loadProfileData(token);
loadProfilePic(token);
return profile;
}
loadProfileData(Token)and loadProfilePic(Token) contain the Volley requests. onSuccess(...) is called, when the request returns successfully.
loadProfileData(Token):
/**
* Loads the profile data (user's name and email address) for the given token and sets it in the appropriate profile. If there is no profile for the given token, nothing happens.
*
* #param token token, for which the profile data should be loaded
*/
private void loadProfileData(final Token token) {
final ProfileDrawerItem profile = profilesByUserId.get(token.getUserId());
if (profile == null)
return;
// Load name and email address
Callback<User> callbackUser = new Callback<User>() {
#Override
public void onSuccess(User user) {
profile.withName(String.format("%s %s", user.getFirstName(), user.getLastName()));
profile.withEmail(user.getUsername());
profile.withNameShown(true);
}
#Override
public void onError(String error) {
// not relevant
}
};
loadUserById(token.getUserId(), Source.LOCAL_OVER_REMOTE, callbackUser, token);
}
loadProfilePic(Token):
/**
* Loads the profile pic for the given token and sets it in the appropriate profile. If there is no profile for the given token, nothing happens.
*
* #param token token, for which the profile pic should be loaded
*/
private void loadProfilePic(final Token token) {
final ProfileDrawerItem profile = profilesByUserId.get(token.getUserId());
if (profile == null)
return;
// Load profile pic
Callback<File> callbackPic = new Callback<File>() {
#Override
public void onSuccess(File profilePic) {
Bitmap bitmap = FileUtils.createBitmap(profilePic);
// Crop center square of the bitmap
int dimension = Math.min(bitmap.getWidth(), bitmap.getHeight());
bitmap = ThumbnailUtils.extractThumbnail(bitmap, dimension, dimension);
profile.withIcon(bitmap);
}
#Override
public void onError(String error) {
// Not relevant
}
};
loadProfilePicById(token.getUserId(), true, callbackPic, true);
}
My expectation is that after onSuccess(File) has executed, the new profile pic should be shown in the Account Header and in the account list below the header. However, the pic, name and email in the Account Header are not updated. Only after switching profiles, which executes getAccountHeader().setActiveProfile(token.getId()), the correct data and pic are shown in the Account Header.
To me, this looks, like it was an issue with MaterialDrawer, but I'm not sure about that. And even if it were, maybe there is a workaround.
Does any one know, how I can get MaterialDrawer to refresh the Account Header, after changing a profile, or how else I can solve this issue?
My overall goal is to be able to automatically download a daily report using the bing ads API. To do this, I need to authenticate with OAuth (the old PasswordAuthentication method doesn't work because I have a new microsoft account). I have been through the "Authorization Code Grant Flow" manually and authorised myself successfully. The problem is:
the token is only valid for 1 hour
when the token expires, the process requires the user to manually login using a web browser again and re-allow the app access
Here's an example desktop app using OAuth
Does somebody know either
a more fitting way of authenticating?
or a way of bypassing the user interaction?
SOLUTION:
As mentioned by #eric urban it is only necessary to authorize manually, once. after that, the refresh token will do. (Not really obvious just looking at the example desktop app!)
I wrote a class to deal with all the OAuth stuff and persist the refresh token to a file
public class OAuthRefreshToken {
private static String refreshTokenFileName = "./bingAdsRefreshToken.txt";
private static String ClientId = "XXXXX";
private final OAuthDesktopMobileAuthCodeGrant oAuthDesktopMobileAuthCodeGrant = new OAuthDesktopMobileAuthCodeGrant(ClientId);
private String refreshToken;
public OAuthRefreshToken() {
oAuthDesktopMobileAuthCodeGrant.setNewTokensListener(new NewOAuthTokensReceivedListener() {
#Override
public void onNewOAuthTokensReceived(OAuthTokens newTokens) {
String refreshTime = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
.format(new java.util.Date());
refreshToken = newTokens.getRefreshToken();
System.out.printf("Token refresh time: %s\n", refreshTime);
writeRefreshTokenToFile();
}
});
getRefreshTokenFromFile();
refreshAccessToken();
}
public OAuthRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
writeRefreshTokenToFile();
}
public OAuthDesktopMobileAuthCodeGrant getoAuthDesktopMobileAuthCodeGrant() {
return oAuthDesktopMobileAuthCodeGrant;
}
private void refreshAccessToken(){
oAuthDesktopMobileAuthCodeGrant.requestAccessAndRefreshTokens(refreshToken);
}
private void getRefreshTokenFromFile(){
try {
refreshToken = readFile(refreshTokenFileName, Charset.defaultCharset());
} catch (IOException e) {
e.printStackTrace();
}
}
private static String readFile(String path, Charset encoding)
throws IOException
{
byte[] encoded = Files.readAllBytes(Paths.get(path));
return new String(encoded, encoding);
}
private void writeRefreshTokenToFile(){
File refreshTokenFile = new File(refreshTokenFileName);
try {
FileWriter f2 = new FileWriter(refreshTokenFile);
f2.write(refreshToken);
f2.close();
} catch (IOException e) {
e.printStackTrace();
return;
}
System.out.printf("New refresh token: %s\n", refreshToken);
System.out.printf("Stored Safely in: %s\n", refreshTokenFileName);
}
}
Use it in your app like:
final OAuthRefreshToken oAuthRefreshToken = new OAuthRefreshToken();
final OAuthDesktopMobileAuthCodeGrant oAuthDesktopMobileAuthCodeGrant = oAuthRefreshToken.getoAuthDesktopMobileAuthCodeGrant();
You are correct that user consent is required up front (once). Thereafter you can use the refresh token to request additional access tokens without user interaction. For details about Authorization Code grant flow using the Bing Ads Java SDK please see Getting Started Using Java with Bing Ads Services. Does this help?
The refresh token should not expire that quickly, they are usually permanent or last a very long time. These can however be revoked, or invalidated if you request too many of them. i believe when you have requested more than 25 different refresh tokens, they older ones start to become invalid.