Check purchase with google billing at startup - java

I'm currently using the Google Billing API to check at startup of my android app if the user has bought the premium package.
I'm using the queryPurchases() method to parse the purchases. I would like to ask if this is the correct way to check a purchase without using a server. I've read about the 200000 API limit - is there a better way to implement this?
This is the current flutter method that I run at every startup:
Future<void> initBillingPlatform() async {
try {
await FlutterInappPurchase.instance.initConnection;
List<PurchasedItem> puchased = await FlutterInappPurchase.instance.getAvailablePurchases();
String result;
bool purchased = false;
for (int i = 0; i < puchased.length; i++) {
PurchasedItem item = puchased.elementAt(i);
if ((item.productId == "premium_features_developer" || item.productId == "premium_features") &&
item.purchaseStateAndroid != null &&
item.purchaseStateAndroid == 1) {
purchased = true;
if (!item.isAcknowledgedAndroid) {
result = await FlutterInappPurchase.instance.acknowledgePurchaseAndroid(item.purchaseToken);
print(result);
}
}
}
isPremium = purchased;
} on Exception catch (e) {
//Se arriva in errore il default è premium=false
print(e);
}
FlutterInappPurchase.instance.endConnection;
}
The method getAvailablePurchases() of this flutter package runs the following code:
final String type = call.argument("type");
final JSONArray items = new JSONArray();
Purchase.PurchasesResult purchasesResult = billingClient.queryPurchases(type.equals("subs") ? BillingClient.SkuType.SUBS : BillingClient.SkuType.INAPP);
final List<Purchase> purchases = purchasesResult.getPurchasesList();
try {
if (purchases != null) {
for (Purchase purchase : purchases) {
JSONObject item = new JSONObject();
item.put("productId", purchase.getSku());
item.put("transactionId", purchase.getOrderId());
item.put("transactionDate", purchase.getPurchaseTime());
item.put("transactionReceipt", purchase.getOriginalJson());
item.put("orderId", purchase.getOrderId());
item.put("purchaseToken", purchase.getPurchaseToken());
item.put("developerPayloadAndroid", purchase.getDeveloperPayload());
item.put("signatureAndroid", purchase.getSignature());
item.put("purchaseStateAndroid", purchase.getPurchaseState());
if (type.equals(BillingClient.SkuType.INAPP)) {
item.put("isAcknowledgedAndroid", purchase.isAcknowledged());
} else if (type.equals(BillingClient.SkuType.SUBS)) {
item.put("autoRenewingAndroid", purchase.isAutoRenewing());
}
items.put(item);
}
result.success(items.toString());
}
} catch (JSONException je) {
result.error(call.method, je.getMessage(), je.getLocalizedMessage());
} catch (FlutterException fe) {
result.error(call.method, fe.getMessage(), fe.getLocalizedMessage());
}

Related

use setPlaybackParameters in MediaControllerCompat

Following this demo: https://github.com/googlesamples/android-media-controller
I have this
if (playbackState == null) {
Log.e(TAG, "Failed to update media info, null PlaybackState.");
return null;
}
Map<String, String> mediaInfos = new HashMap<>();
mediaInfos.put(getString(R.string.info_state_string),
playbackStateToName(playbackState.getState()));
MediaMetadataCompat mediaMetadata = mController.getMetadata();
if (mediaMetadata != null) {
addMediaInfo(
mediaInfos,
getString(R.string.info_title_string),
mediaMetadata.getString(MediaMetadataCompat.METADATA_KEY_TITLE));
addMediaInfo(
mediaInfos,
getString(R.string.info_artist_string),
mediaMetadata.getString(MediaMetadataCompat.METADATA_KEY_ARTIST));
addMediaInfo(
mediaInfos,
getString(R.string.info_album_string),
mediaMetadata.getString(MediaMetadataCompat.METADATA_KEY_ALBUM));
binding.controlsPage.mediaTitle.setText(
mediaMetadata.getString(MediaMetadataCompat.METADATA_KEY_TITLE));
binding.controlsPage.mediaArtist.setText(
mediaMetadata.getString(MediaMetadataCompat.METADATA_KEY_ARTIST));
binding.controlsPage.mediaAlbum.setText(
mediaMetadata.getString(MediaMetadataCompat.METADATA_KEY_ALBUM));
final Bitmap art = mediaMetadata.getBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART);
if (art != null) {
binding.controlsPage.mediaArt.setImageBitmap(art);
} else {
binding.controlsPage.mediaArt.setImageResource(R.drawable.ic_album_black_24dp);
}
// Prefer user rating, but fall back to global rating if available.
RatingCompat rating =
mediaMetadata.getRating(MediaMetadataCompat.METADATA_KEY_USER_RATING);
if (rating == null) {
rating = mediaMetadata.getRating(MediaMetadataCompat.METADATA_KEY_RATING);
}
mRatingUiHelper.setRating(rating);
} else {
binding.controlsPage.mediaArtist.setText(R.string.media_info_default);
binding.controlsPage.mediaArt.setImageResource(R.drawable.ic_album_black_24dp);
mRatingUiHelper.setRating(null);
}
final long actions = playbackState.getActions();
`
I'm interested in getting the current pitch and changing it to the one I want.
I can see this api here https://developer.android.com/reference/androidx/media3/session/MediaController#setPlaybackParameters(androidx.media3.common.PlaybackParameters) does what I want, but it's only for the MediaController, not for the MediaControllerCompat.
I tried doing mController.getMediaController()
https://developer.android.com/reference/kotlin/android/support/v4/media/session/MediaControllerCompat#getMediaController() with no changes at all.
Any ideas?

Spring Rest API TransactionSystemException

I'm very new in spring development because I'm not a back developer.
I create a back to manage sports training.
I has several times a TransactionSystemException like
Exceptionorg.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction
I don't understand what it mean.
I have a class Person who contains a Coordinates object on #OneToOne relation.
Each class have a Service class that has a method of adding.
In PersonService's add method, I call the Coordinates add method which save and return saved object.
This is the add method of PersonClass
public ResponseService<ObjectCreatedModel<UUID, PersonneMorale>> add(PersonneMorale personneMorale) {
String messageErreur = TAG + " - add - ";
StatusReturn status = StatusReturn.ERROR;
String message = null;
ObjectCreatedModel<UUID, PersonneMorale> objectCreatedModel = null;
if (personneMorale != null) {
if (personneMorale.getId() == null) {
personneMorale.setId(UUID.randomUUID());
try {
// Gestion des coordonnees
ResponseService<ObjectCreatedModel<UUID, Coordonnees>> responseServiceCoordonnees =
coordonneesService.add(personneMorale.getCoordonnees());
if (responseServiceCoordonnees.isSuccess() || responseServiceCoordonnees.exist()) {
ResponseService<Coordonnees> responseServiceCoordonneesGet = coordonneesService
.getOne(responseServiceCoordonnees.getObjectReturn().getId());
Coordonnees coordonnees = responseServiceCoordonneesGet.getObjectReturn();
personneMorale.setCoordonnees(coordonnees);
personneMorale = personneMoraleRepository.save(personneMorale);
if (personneMorale != null) {
status = StatusReturn.SUCCESS;
objectCreatedModel = new ObjectCreatedModel<>(personneMorale.getId(), null);
} else {
message = messageErreur + StringResource.E_OCCURRED;
}
} else {
status = responseServiceCoordonnees.getStatusReturn();
message = responseServiceCoordonnees.getMessage();
}
} catch (ConstraintViolationException violationException) {
status = StatusReturn.EXCEPTION;
message = messageErreur + ConstraintViolationReader.extractException(violationException);
} catch (Exception ex) {
status = StatusReturn.EXCEPTION;
message = messageErreur + ex.toString();
}
} else {
message = messageErreur + StringResource.E_MUST_NULL;
}
} else {
message = messageErreur + StringResource.E_SET_PARAMETER;
}
return new ResponseService<>(status, message, objectCreatedModel);
}
This is the add method of CoordinatesService
public ResponseService<ObjectCreatedModel<UUID, Coordonnees>> add(Coordonnees coordonnees) {
StatusReturn status = StatusReturn.ERROR;
String message = "";
ObjectCreatedModel<UUID, Coordonnees> objectCreatedModel = null;
if (coordonnees != null) {
if (coordonnees.getIdCoordonnees() == null) {
try {
coordonnees.setIdCoordonnees(UUID.randomUUID());
Coordonnees coordonneesBase = coordonneesRepository.save(coordonnees);
if (coordonneesBase != null) {
status = StatusReturn.SUCCESS;
objectCreatedModel = new ObjectCreatedModel<>(coordonneesBase.getIdCoordonnees(), null);
} else {
message = StringResource.E_ERROR_OCCURRED;
}
} catch (ConstraintViolationException violationException) {
status = StatusReturn.EXCEPTION;
message = "Exception" + ConstraintViolationReader.extractException(violationException);
} catch (Exception ex) {
status = StatusReturn.EXCEPTION;
message = "Exception" + ex.toString();
}
} else {
message = "Coordonnées" + ErrorsString.ERROR_COMMON_ID_MUST_BE_EMPTY;
}
} else {
message = ErrorsString.ERROR_COORDINATES_MANDATORY;
}
return new ResponseService<>(status, message, objectCreatedModel);
}
The error occurs when CoordinatesService try to save coordinates and pass to the catch (Exception e)
Could you help me to understand what Transaction error mean with an example like my code please ?

Realm Not saving datas

I am trying save the data's which i get from API and my function is as below
try {
// Simulate network access.
mNetworkSubscription = NetworkRequest.performAsyncRequest(api.getPatientsData(tenantID), (data) -> {
// Update UI on main thread
try {
// long pat_id = 0;
if(data != null) {
if(data.getAsJsonObject().get("error") != null){
publishResults("getPatientsData",STATUS_ERROR, null);
}
if (data != null && data.get("result") != null && data.get("result").toString() != "false") {
Realm realm = Realm.getDefaultInstance();
JsonParser parser = new JsonParser();
// realm.beginTransaction();
// realm.where(Patient.class).findAll().deleteAllFromRealm();
//realm.commitTransaction();
try {
JsonArray casesJsonArray = data.get("result").getAsJsonArray();//parser.parse(data.get("result").getAsJsonObject().toString()).getAsJsonArray();
Log.v(Constants.TAG,"PatientJsonArray: "+casesJsonArray);
if(casesJsonArray.size() > 0) {
Patient patientRecord = new Patient();
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
for (int i = 0; i < casesJsonArray.size(); i++) {
try {
JsonObject jsonObject = (JsonObject) casesJsonArray.get(i);
Log.e(Constants.TAG, "execute: jsonObject "+jsonObject);
Log.e(Constants.TAG, "execute: id "+id);
if(realm.where(Patient.class).max("id") != null) {
id = realm.where(Patient.class).max("id").intValue();
Log.e(Constants.TAG, "execute:getPatientsData : In DataSync: " + id);
}
Log.e(Constants.TAG, "execute: jsonObject "+jsonObject);
Log.e(Constants.TAG, "execute: id "+pat_id);
patientRecord.setId(id + 1);
patientRecord.setTenantID(jsonObject.get("tenant_id").getAsLong());
patientRecord.setPatientID(jsonObject.get("patient_id").getAsLong());
patientRecord.setFirstName(jsonObject.get("first_name").getAsString());
patientRecord.setLastName(jsonObject.get("last_name").getAsString());
patientRecord.setPatientWeight(jsonObject.get("patient_weight").getAsString());
patientRecord.setPatientAge(jsonObject.get("patient_age").getAsString());
patientRecord.setGender(jsonObject.get("gender").getAsString());
patientRecord.setStreetAddress(jsonObject.get("street_address").getAsString());
patientRecord.setArea(jsonObject.get("area").getAsString());
patientRecord.setCity(jsonObject.get("city").getAsString());
patientRecord.setZipcode(jsonObject.get("zipcode").getAsString());
patientRecord.setState(jsonObject.get("state").getAsString());
patientRecord.setEmail(jsonObject.get("email").getAsString());
patientRecord.setEnqNumber(jsonObject.get("alternate_number").getAsString());
patientRecord.setEnqName(jsonObject.get("enquirer_name").getAsString());
realm.copyToRealmOrUpdate(patientRecord);
}catch (Exception e){
Log.v(Constants.TAG,"caseList Insert exception: "+e.toString());
}
}
}
});
//realm.close();
}
} catch (Exception e) {
Log.v(Constants.TAG, "getPatientsData Exception: " + e.toString());
}
realm.close();
}
}
} catch (Exception e) {
Log.e(Constants.TAG, "getPatientsData() exception: " + e.toString());
publishResults("getPatientsData",STATUS_ERROR, null);
}finally {
publishResults("getPatientsData",STATUS_FINISHED, null);
}
}, (error) -> {
// Handle Error
Log.e(Constants.TAG,"getPatientsData() Error: "+error.getCause());
publishResults("getPatientsData",STATUS_ERROR, null);
});
}catch (Exception e){
Log.e(Constants.TAG,"getPatientsData() Exception: "+e.toString());
publishResults("getPatientsData",STATUS_ERROR, null);
}
But realm.copyOrUpdate(patientRecords);
is not creating/saving a record in local.
First confirm, if there is no exceptions arises?
Try following workarounds,
Check casesJsonArray.size() > 0 must be true. (OR)
Remove realm.close() and try it once. Sometime it will close realm database connection in between the operation. (OR)
Try with hardcoded(dummy) data.
If above doesn't work then post your Patient Model here.
Note: For auto increment of id you can use following code which will reduce no of database calls-
Number num = realm.where(Patient.class).max("id");
int l_id;
if (num == null) {
i_id = 0;
} else {
i_id = num.intValue() + 1;
}

How to reliably detect device type on a MediaRoute select/unselect event

I have dug into the Android sources and found that under the hood, each time an Audio route event occurs, an AudioRoutesInfo object is based to the internal updateAudioRoutes method in MediaRouter:
void updateAudioRoutes(AudioRoutesInfo newRoutes) {
if (newRoutes.mMainType != mCurAudioRoutesInfo.mMainType) {
mCurAudioRoutesInfo.mMainType = newRoutes.mMainType;
int name;
if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_HEADPHONES) != 0
|| (newRoutes.mMainType&AudioRoutesInfo.MAIN_HEADSET) != 0) {
name = com.android.internal.R.string.default_audio_route_name_headphones;
} else if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
name = com.android.internal.R.string.default_audio_route_name_dock_speakers;
} else if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_HDMI) != 0) {
name = com.android.internal.R.string.default_media_route_name_hdmi;
} else {
name = com.android.internal.R.string.default_audio_route_name;
}
sStatic.mDefaultAudioVideo.mNameResId = name;
dispatchRouteChanged(sStatic.mDefaultAudioVideo);
}
final int mainType = mCurAudioRoutesInfo.mMainType;
boolean a2dpEnabled;
try {
a2dpEnabled = mAudioService.isBluetoothA2dpOn();
} catch (RemoteException e) {
Log.e(TAG, "Error querying Bluetooth A2DP state", e);
a2dpEnabled = false;
}
if (!TextUtils.equals(newRoutes.mBluetoothName, mCurAudioRoutesInfo.mBluetoothName)) {
mCurAudioRoutesInfo.mBluetoothName = newRoutes.mBluetoothName;
if (mCurAudioRoutesInfo.mBluetoothName != null) {
if (sStatic.mBluetoothA2dpRoute == null) {
final RouteInfo info = new RouteInfo(sStatic.mSystemCategory);
info.mName = mCurAudioRoutesInfo.mBluetoothName;
info.mDescription = sStatic.mResources.getText(
com.android.internal.R.string.bluetooth_a2dp_audio_route_name);
info.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO;
sStatic.mBluetoothA2dpRoute = info;
addRouteStatic(sStatic.mBluetoothA2dpRoute);
} else {
sStatic.mBluetoothA2dpRoute.mName = mCurAudioRoutesInfo.mBluetoothName;
dispatchRouteChanged(sStatic.mBluetoothA2dpRoute);
}
} else if (sStatic.mBluetoothA2dpRoute != null) {
removeRouteStatic(sStatic.mBluetoothA2dpRoute);
sStatic.mBluetoothA2dpRoute = null;
}
}
if (mBluetoothA2dpRoute != null) {
if (mainType != AudioRoutesInfo.MAIN_SPEAKER &&
mSelectedRoute == mBluetoothA2dpRoute && !a2dpEnabled) {
selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo, false);
} else if ((mSelectedRoute == mDefaultAudioVideo || mSelectedRoute == null) &&
a2dpEnabled) {
selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute, false);
}
}
}
Unfortunately, the only thing I have found that is exposed about the device type in the MediaRouter callbacks, is the internal string resource name of the device (e.g. Phone or Headphones). However, you can see that under the hood, this AudioRoutesInfo object has references to whether the device was a headphone, HDMI etc.
Has anyone found a solution to get at this information? The best way I have found is to use the internal resource names, which is pretty ugly. God, if they would just provide the AudioRoutesInfo object all this information could be accessed without having to rely on a resource hack.

android-market retrieve empty results

I wanted to harvest some data on specific apps in
Google play marketplace.
I have used this unofficial API:
https://code.google.com/p/android-market-api/
Here is my code, that basically gets list of apps' names
and try to fetch other data on each app:
public void printAllAppsData(ArrayList<AppHarvestedData> dataWithAppsNamesOnly)
{
MarketSession session = new MarketSession();
session.login("[myGamil]","[myPassword]");
session.getContext().setAndroidId("dead00beef");
final ArrayList<AppHarvestedData> finalResults = new ArrayList<AppHarvestedData>();
for (AppHarvestedData r : dataWithAppsNamesOnly)
{
String query = r.name;
AppsRequest appsRequest = AppsRequest.newBuilder()
.setQuery(query)
.setStartIndex(0).setEntriesCount(10)
//.setCategoryId("SOCIAL")
.setWithExtendedInfo(true)
.build();
session.append(appsRequest, new Callback<AppsResponse>() {
#Override
public void onResult(ResponseContext context, AppsResponse response) {
List<App> apps = response.getAppList();
for (App app : apps) {
AppHarvestedData r = new AppHarvestedData();
r.title = app.getTitle();
r.description = app.getExtendedInfo().getDescription();
String tmp = app.getExtendedInfo().getDownloadsCountText();
tmp = tmp.replace('<',' ').replace('>',' ');
int indexOf = tmp.indexOf("-");
tmp = (indexOf == -1) ? tmp : tmp.substring(0, indexOf);
r.downloads = tmp.trim();
r.rating = app.getRating();
r.version = app.getVersion();
r.userRatingCount = String.valueOf(app.getRatingsCount());
finalResults.add(r);
}
}
});
session.flush();
}
for(AppHarvestedData res : finalResults)
{
System.out.println(res.toString());
}
}
}
Should I realyy call session.flush(); at this point?
all my quesries return empty collection as a result,
even though I see there are some names as input.
It works fine when I send only one hard coded app name as a query.
session.flush() close you session
you should open session for each query. pay attention the user can be locked for few minutes so you should have many users to to those queries.
if you have the AppId you should use that query:
String query = r.name;
AppsRequest appsRequest = AppsRequest.newBuilder()
.setAppId("com.example.android")
.setWithExtendedInfo(true)
.build();
session.append(appsRequest, new Callback<AppsResponse>() {
#Override
public void onResult(ResponseContext context, AppsResponse response) {
List<App> apps = response.getAppList();
for (App app : apps) {
AppHarvestedData r = new AppHarvestedData();
r.title = app.getTitle();
r.description = app.getExtendedInfo().getDescription();
String tmp = app.getExtendedInfo().getDownloadsCountText();
tmp = tmp.replace('<',' ').replace('>',' ');
int indexOf = tmp.indexOf("-");
tmp = (indexOf == -1) ? tmp : tmp.substring(0, indexOf);
r.downloads = tmp.trim();
r.rating = app.getRating();
r.version = app.getVersion();
r.userRatingCount = String.valueOf(app.getRatingsCount());
finalResults.add(r);
}
}
});
session.flush();
}
if you want to download also screenshoot or images you should call this query:
GetImageRequest? imgReq; imgReq = GetImageRequest?.newBuilder().setAppId(appId).setImageUsage(AppImageUsage?.SCREENSHOT).setImageId("0").build();
session.append(imgReq, new Callback<GetImageResponse>() {
#Override public void onResult(ResponseContext? context, GetImageResponse? response) {
Log.d(tag, "------------------> Images response <----------------"); if(response != null) {
try {
//imageDownloader.download(image, holder.image, holder.imageLoader);
Log.d(tag, "Finished downloading screenshot 1...");
} catch(Exception ex) {
ex.printStackTrace();
}
} else {
Log.e(tag, "Response was null");
} Log.d(tag, "------------> End of Images response <------------");
}
});

Categories