Related
My app sends an activation code to email during user's registration process, but for an unknown -to me- reason in some Chinese devices is not working and I'm not being able to get a log of the Exception.
This is where I send the email:
private void getAwsResult_getAwsUser(String strResult)
{
try{
CommunityUserDTO communityUser = CommunityUserService.deserializeUser(strResult);
if(communityUser==null){ //email don't exist in database
registerResult.setValue(new Pair<>(null, Enum.RegisterResult.SENDINGEMAIL));
CreateUserUseCase.setActivationCode(this.user);
sendConfirmationEmail(this.user.getEmail(), this.user.getActivationCode());
}else{
registerResult.setValue(new Pair<>(null, Enum.RegisterResult.EMAILALREADYREGISTERED));
}
}catch(Exception ex)
{
ExceptionHandler.logException(ex);
}
}
private void sendConfirmationEmail(String recipient, int activationCode)
{
String jsonEmail = SendEmailUseCase.
generateJsonConfirmationEmail(recipient, activationCode);
TaskRunner taskRunner = new TaskRunner();
taskRunner.executeAsync(new SendEmailTask(jsonEmail), this::getAwsResult_sendEmail);
}
public class SendEmailTask implements Callable<String>
{
private final String jsonEmail;
public SendEmailTask(String jsonEmail)
{
this.jsonEmail = jsonEmail;
}
#Override
public String call()
{
return TMEmail.send(jsonEmail);
}
}
This is a jsonEmail input parameter for sendEmail method example:
{
"subject":"Your activation code.",
"message":"Your activation code is: xxxxxx",
"send_to":"xxx#gmail.com",
"send_from":"contact#xxx.com",
"smtp_server":"xxx.amazonaws.com",
"smtp_user":"xxx",
"smtp_pass":"xxx",
"smtp_useauth":"true",
"smtp_port":"25",
"center_text":"true"
}
public static String send(String jsonEmail)
{
String result = Enum.Result.OK;
JSONObject oJsonEmail = TMJson.str2JSON(jsonEmail);
try {
String subject = oJsonEmail.getString(Enum.Email.SUBJECT);
String message = oJsonEmail.getString(Enum.Email.MESSAGE);
String sendFrom = oJsonEmail.getString(Enum.Email.SEND_FROM);
String sendTo = oJsonEmail.getString(Enum.Email.SEND_TO);
String smtpServer = oJsonEmail.getString(Enum.Email.SMTP_SERVER);
String smtpPort = oJsonEmail.getString(Enum.Email.SMTP_PORT);
String smtpUseAuth = oJsonEmail.getString(Enum.Email.SMTP_USEAUTH);
boolean centerBodyText = Boolean.parseBoolean(oJsonEmail.getString(Enum.Email.CENTER_TEXT));
final String smtpUser = oJsonEmail.getString(Enum.Email.SMTP_USER);
final String smtpPass = oJsonEmail.getString(Enum.Email.SMTP_PASS);
Properties props = new Properties();
props.put("mail.smtp.host", smtpServer);
props.put("mail.smtp.port", smtpPort);
props.put("mail.smtp.auth", smtpUseAuth);
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.from.alias", "My App Name");
Session session = Session.getInstance(props,
new javax.mail.Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(smtpUser, smtpPass);
}
});
message = TMJson.decodeEmail(message);
message = format(message, centerBodyText);
Message oMessage = new MimeMessage(session);
oMessage.setFrom(new InternetAddress(sendFrom, session.getProperty("mail.from.alias"), "UTF-8"));
oMessage.setRecipients(Message.RecipientType.TO, InternetAddress.parse(sendTo));
oMessage.setSubject(subject);
oMessage.setContent(message, "text/html; charset=iso-8859-2");
Transport.send(oMessage);
}catch (MessagingException | UnsupportedEncodingException | JSONException e) {
result = Enum.Result.KO;
ExceptionHandler.logException(e);
}
return result;
}
I've tested this in my Android mobile and tablet, and in many other devices and it's working just fine, the email is sent and the user receives the activation code, but in China, where we are still testing the beta version the tester is not receiving the email.
I know the app is crashing before sending the email, but cannot figure out why, and if you notice, I hace a try/catch with this ExceptionHandler.logException(e); that sends a report of the crash to my email, but it's also not working, so I don't know what to do.
I contacted AWS support team to check if there is any possibility to see any SES logs, but has no response yet.
Any ideas on how could I get closer to the real exception? Still cannot even see it in the Google Play Developer Console crashes report section.
Ok, luckily I found the issue myself. Nothing to do with Chinese devices, but with Android version.
I was testing my app in Android 10 devices (the only ones I have) and they were testing in an Android 11 device.
This link solved my problem:
Android Studio VerifyError rejecting class text_plain from JavaMail API
And this is the solution: Add this in build.gradle
android {
...
packagingOptions {
exclude 'META-INF/NOTICE.md'
exclude 'META-INF/LICENSE.md'
}
}
dependencies {
implementation 'com.sun.mail:android-mail:1.6.6'
implementation 'com.sun.mail:android-activation:1.6.6'
...
}
Hope I'm helping anyone else having problems sending emails.
I have an app connected with Azure backend. I created a login and some api calls 2 months ago. They worked fine until a few days ago and then it starts to fail "sometimes".
The login log onFailure says: Error while authenticating user
The callback log onFailure says: Error while processing request
And the cause of both says : stream was reset: PROTOCOL_ERROR
This post is to similar to this but didn't work.
Some code here:
LoginFragment.java
private void login(String email, String password){
loginProgressBar.setVisibility(View.VISIBLE);
try {
JsonObject params = new JsonObject();
params.addProperty("Username", email);
params.addProperty("Password", password);
ListenableFuture<MobileServiceUser> listenable = Client.logIn(getContext(), params);
Futures.addCallback(listenable, new FutureCallback<MobileServiceUser>() {
#Override
public void onSuccess(MobileServiceUser mobileServiceUser) {
loginProgressBar.setVisibility(View.GONE);
SharedPreferences settings = getActivity().getSharedPreferences(Client.MS_USER,0);
SharedPreferences.Editor editor = settings.edit();
Client.clientId = mobileServiceUser.getUserId();
Client.token = mobileServiceUser.getAuthenticationToken();
editor.putString(Client.MS_USER_ID, Client.clientId);
editor.putString(Client.MS_AUTH_TOKEN, Client.token);
editor.apply();
Client.getInstance(getContext()).setCurrentUser(mobileServiceUser);
Intent i = new Intent(getContext(), MainActivity.class);
startActivity(i);
}
#Override
public void onFailure(Throwable t) {
loginProgressBar.setVisibility(View.GONE);
Throwable t2 = t.getCause();
Throwable t3 = t2.getCause();
Log.e("LoginFail", t.getMessage());
Log.e("LoginFail", t2.getMessage());
if(t3 != null){
Log.e("LoginFail", t3.getMessage());
}
Toast.makeText(getContext(), getResources().getString(R.string.bad_login), Toast.LENGTH_LONG).show();
}
}, MoreExecutors.directExecutor());
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
Client.java
public class Client {
public static final String MS_USER = "MS_USER";
public static final String MS_USER_ID = "MS_USER_ID";
public static final String MS_AUTH_TOKEN = "MS_AUTH_TOKEN";
public static String clientId;
public static String token;
private static MobileServiceClient instance = null;
public static MobileServiceClient getInstance(Context context) {
if (instance ==null){
try {
instance = new MobileServiceClient(Env.AZURE_URL, context);
instance.setAndroidHttpClientFactory(() -> {
OkHttpClient client = new OkHttpClient();
client.setReadTimeout(20, TimeUnit.SECONDS);
client.setWriteTimeout(20, TimeUnit.SECONDS);
return client;
});
} catch (MalformedURLException e) {
e.printStackTrace();
}
} else{
instance.setContext(context);
}
return instance;
}
public static ListenableFuture<MobileServiceUser> logIn(Context context, JsonObject parameters) throws MalformedURLException {
String deviceID = "gcm:" + Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
parameters.addProperty("device_id", deviceID);
parameters.addProperty("device_dateTime", Env.DATE_FORMAT.format(new Date()));
parameters.addProperty("device_timeZone", API.getTimezone());
parameters.addProperty("device_language", Env.LANGUAGE);
parameters.addProperty("app", Env.APP_NAME);
return getInstance(context).login("auth", parameters);
}
public static ListenableFuture<JsonElement> callApi(Context context, String apiName, JsonObject parameters, String httpMethod){
if(httpMethod.equals("POST")){
String deviceID = "gcm:" + Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
parameters.addProperty("user_id", Client.clientId);
parameters.addProperty("device_id", deviceID);
parameters.addProperty("device_dateTime", Env.DATE_FORMAT.format(new Date()));
parameters.addProperty("device_timeZone", API.getTimezone());
parameters.addProperty("device_language", Env.LANGUAGE);
parameters.addProperty("app", Env.APP_NAME);
parameters.addProperty("role", "Patient");
return getInstance(context).invokeApi(apiName, parameters, httpMethod, null);
} else {
return getInstance(context).invokeApi(apiName, null, httpMethod, null);
}
}
This is probably related to an issue in Azure App Service that is weirdly enough not reported on the public Azure status page.
The message that affected Azure client received was (quoted from the link above):
Starting at 02:00 UTC on 3 Apr 2018, you have been identified as a
customer using App Services who may have received connection failure
notifications when using Android apps with older HTTP clients or
desktop browsers using cross-site scripting calls. Engineers have
identified an issue with a recent deployment and are investigating
mitigation options. Customers experiencing this issue can
self-mitigate by updating the site config setting "http20Enabled" to
false via resources.azure.com. Instructions on how to update site
config can be found here:
https://azure.microsoft.com/en-us/blog/azure-resource-explorer-a-new-tool-to-discover-the-azure-api/
Go to https://resources.azure.com/
Make sure you are in Read/Write mode by clicking in the option to the
left of your name.
Find the affected site and browse to Config > Web:
https://resources.azure.com/subscriptions//resourceGroups//providers/Microsoft.Web/sites//config/web
Change the property: "http20Enabled": from true to false by clicking
in Edit properties, Update to “false” and then clicking PUT to save
change.
If you have tried these steps and are continuing to experience issues
with your App Service, please create a technical support ticket to
further troubleshoot: aka.ms/azsupt. This message will be closed in 7
days.
After getting the following code to work reliably for a month or so, it stopped working reliably a couple of days ago. About half the time it returns a properly translated string and the other half of the time it returns one of the following two messages:
java.io.FileNotFoundException:
https://api.cognitive.microsoft.com/sts/v1.0/issueToken
java.net.UnknownHostException: Unable to resolve host
"api.microsofttranslator.com": No address associated with hostname
The timing of this problem's beginning coincided with the expiration of my free azure cognitive services account however I migrated to a pay-as-you-go account yesterday and the problem continues.
Why is this happening?
static class translateMessageX extends AsyncTask<String, Void, String>
{
//input string array of 3 items
//[0]is the message to be translated
//[1]is the from language i.e. "english"
//[2]is the to language i.e. "spanish"
//[3]"echo" or "received"
String retString;
String inString = null;
String messageType = null;
String URLHolder = ""; //hold the URL here while we are translating the text
#Override
protected String doInBackground(String... params)
{
inString = params[0];
String from = params[1];
String to = params[2];
messageType = params[3];
int urlStart = inString.indexOf("http");
if (!(urlStart == -1))
{
URLHolder = inString.substring(urlStart);
inString = inString.substring(0, urlStart -1);
}
else
{
URLHolder = "";
}
Integer mesChars = params[0].length();
Integer tCharsLeft = GlobalStuff.getTranslationsFromSP();
if (tCharsLeft > 0)
{
if (tCharsLeft < mesChars) //we charge for both 'echo' and 'received' translations
{
GlobalStuff.updateTranslationInventory(tCharsLeft * -1);
}
else
{
GlobalStuff.updateTranslationInventory(mesChars * -1);
}
GlobalStuff.notifyListeners(this, "#uui", "notused", "notused" );
try
{
Language fromLang = GlobalStuff.getLang(from);
Language toLang = GlobalStuff.getLang(to);
//retString = Translate.execute(inString, fromLang, toLang);
//String debugstr = "look at retStr";
String authenticationUrl = "https://api.cognitive.microsoft.com/sts/v1.0/issueToken";
HttpsURLConnection authConn = (HttpsURLConnection) new URL(authenticationUrl).openConnection();
authConn.setRequestMethod("POST");
authConn.setDoOutput(true);
authConn.setRequestProperty("Ocp-Apim-Subscription-Key", GlobalStuff.translateKey);
IOUtils.write("", authConn.getOutputStream(), "UTF-8");
String token = IOUtils.toString(authConn.getInputStream(), "UTF-8");
System.out.println(token);
// Using the access token to build the appid for the request url
String appId = URLEncoder.encode("Bearer "+token, "UTF-8");
String text = URLEncoder.encode(inString, "UTF-8");
String translatorTextApiUrl = String.format("https://api.microsofttranslator.com/v2/http.svc/Translate?appid=%s&text=%s&from=%s&to=%s", appId, text, fromLang, toLang);
HttpsURLConnection translateConn = (HttpsURLConnection) new URL(translatorTextApiUrl).openConnection();
translateConn.setRequestMethod("GET");
translateConn.setRequestProperty("Accept", "application/xml");
retString = IOUtils.toString(translateConn.getInputStream(), "UTF-8");
String debug = "look at retString";
}
catch (Exception e)
{
retString = e.toString();
}
}
else
{
retString = "OUT OF TRANSLATION CREDITS - " + inString;
}
return retString;
}
#Override
protected void onPostExecute(String result)
{
//rest of logic should be here??
String debug = "look at result";
String answer = extractTranslation(result);
.. . . .
Host not found looks like a simple connectivity error. These hosts do exist.
You can void the call to the token service by passing the key in the call to api.microsofttranslator.com directly:
https://cognitive.uservoice.com/knowledgebase/articles/1815385-api-translator-text-speech-using-the-api-key
That fixes one of the host not found problems, but not the other.
I would recommend though to not embed the key in the client application. It is safer to call the translator service from your own proxy service, where the proxy is able to safely identify your client as your client.
I have put my app on the Google Play Store. It has been installed by lots of my company's customers. I understand the mechanism of how the app is intended to upgrade.
The users should check the auto-update check box in the Playstore app for each app they want to auto-update. However some users have unchecked it or not checked it in the first place.
The app i have written is for the care industry and is used by carers to deliver homecare. Some of our customers my have 1200 carers. They would have to call all the carers into the office to update the phones individually. This is obviously unacceptable.
Is there a way to programmatically check if there is an updated version of my app on the Play Store?
Could i have code that runs every time the user starts the app that checks the Play Store?
If there is an updated version then the user could be directed to the Playstore. This will mean it is not essential to have the auto-update checked.
Update 17 October 2019
https://developer.android.com/guide/app-bundle/in-app-updates
Update 24 april 2019:
Android announced a feature which will probably fix this problem. Using the in-app Updates API:
https://android-developers.googleblog.com/2018/11/unfolding-right-now-at-androiddevsummit.html
Original:
As far a I know, there is no official Google API which supports this.
You should consider to get a version number from an API.
Instead of connecting to external APIs or webpages (like Google Play Store).
There is a risk that something may change in the API or the webpage, so you should consider to check if the version code of the current app is below the version number you get from your own API.
Just remember if you update your app, you need to change the version in your own API with the app version number.
I would recommend that you make a file in your own website or API, with the version number. (Eventually make a cronjob and make the version update automatic, and send a notification when something goes wrong)
You have to get this value from your Google Play Store page (is changed in the meantime, not working anymore):
<div class="content" itemprop="softwareVersion"> x.x.x </div>
Check in your app if the version used on the mobile is below the version nummer showed on your own API.
Show indication that she/he needs to update with a notification, ideally.
Things you can do
Version number using your own API
Pros:
No need to load the whole code of the Google Play Store (saves on data/bandwidth)
Cons:
User can be offline, which makes checking useless since the API can't be accessed
Version number on webpage Google Play Store
Pros:
You don't need an API
Cons:
User can be offline, which makes checking useless since the API can't be accessed
Using this method may cost your users more bandwidth/mobile data
Play store webpage could change which makes your version 'ripper' not work anymore.
Include JSoup in your apps build.gradle file :
dependencies {
compile 'org.jsoup:jsoup:1.8.3'
}
and get current version like :
currentVersion = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
And execute following thread :
private class GetVersionCode extends AsyncTask<Void, String, String> {
#Override
protected String doInBackground(Void... voids) {
String newVersion = null;
try {
newVersion = Jsoup.connect("https://play.google.com/store/apps/details?id=" + MainActivity.this.getPackageName() + "&hl=it")
.timeout(30000)
.userAgent("Mozilla/5.0 (Windows; U; WindowsNT 5.1; en-US; rv1.8.1.6) Gecko/20070725 Firefox/2.0.0.6")
.referrer("http://www.google.com")
.get()
.select(".hAyfc .htlgb")
.get(7)
.ownText();
return newVersion;
} catch (Exception e) {
return newVersion;
}
}
#Override
protected void onPostExecute(String onlineVersion) {
super.onPostExecute(onlineVersion);
Log.d("update", "Current version " + currentVersion + "playstore version " + onlineVersion);
if (onlineVersion != null && !onlineVersion.isEmpty()) {
if (Float.valueOf(currentVersion) < Float.valueOf(onlineVersion)) {
//show dialog
}
}
}
For more details visit : http://revisitingandroid.blogspot.in/2016/12/programmatically-check-play-store-for.html
Firebase Remote Config could be a possible and reliable solution for now, since google didn't expose any api to it.
Check Firebase Remote Config Docs
Steps
1.Create a firebase project and add google_play_service.json to your project
2.Create keys like "android_latest_version_code" and "android_latest_version_name" in firebase console->Remote Config
3.Android Code
public void initializeFirebase() {
if (FirebaseApp.getApps(mContext).isEmpty()) {
FirebaseApp.initializeApp(mContext, FirebaseOptions.fromResource(mContext));
}
final FirebaseRemoteConfig config = FirebaseRemoteConfig.getInstance();
FirebaseRemoteConfigSettings configSettings = new FirebaseRemoteConfigSettings.Builder()
.setDeveloperModeEnabled(BuildConfig.DEBUG)
.build();
config.setConfigSettings(configSettings);
}
Get current version name and code
int playStoreVersionCode = FirebaseRemoteConfig.getInstance().getString(
"android_latest_version_code");
PackageInfo pInfo = this.getPackageManager().getPackageInfo(getPackageName(), 0);
int currentAppVersionCode = pInfo.versionCode;
if(playStoreVersionCode>currentAppVersionCode){
//Show update popup or whatever best for you
}
4. And keep firebase "android_latest_version_code" and "android_latest_version_name" upto date with your current production version name and code.
Firebase remote config works on both Android and IPhone.
You can get current Playstore Version using JSoup with some modification like below:
#Override
protected String doInBackground(Void... voids) {
String newVersion = null;
try {
newVersion = Jsoup.connect("https://play.google.com/store/apps/details?id=" + MainActivity.this.getPackageName() + "&hl=it")
.timeout(30000)
.userAgent("Mozilla/5.0 (Windows; U; WindowsNT 5.1; en-US; rv1.8.1.6) Gecko/20070725 Firefox/2.0.0.6")
.referrer("http://www.google.com")
.get()
.select(".hAyfc .htlgb")
.get(7)
.ownText();
return newVersion;
} catch (Exception e) {
return newVersion;
}
}
#Override
protected void onPostExecute(String onlineVersion) {
super.onPostExecute(onlineVersion);
Log.d("update", "playstore version " + onlineVersion);
}
answer of #Tarun is not working anymore.
Google has introduced in-app updates API
The API currently supports two flows:
The “immediate” flow is a full-screen user experience that guides the user from download to update before they can use your app.
The “flexible flow” allows users to download the update while continuing to use your app.
There's AppUpdater library.
How to include:
Add the repository to your project build.gradle:
allprojects {
repositories {
jcenter()
maven {
url "https://jitpack.io"
}
}
}
Add the library to your module build.gradle:
dependencies {
compile 'com.github.javiersantos:AppUpdater:2.6.4'
}
Add INTERNET and ACCESS_NETWORK_STATE permissions to your app's Manifest:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
Add this to your activity:
AppUpdater appUpdater = new AppUpdater(this);
appUpdater.start();
#Tarun answer was working perfectly.but now isnt ,due to the recent changes from Google on google play website.
Just change these from #Tarun answer..
class GetVersionCode extends AsyncTask<Void, String, String> {
#Override
protected String doInBackground(Void... voids) {
String newVersion = null;
try {
Document document = Jsoup.connect("https://play.google.com/store/apps/details?id=" + MainActivity.this.getPackageName() + "&hl=en")
.timeout(30000)
.userAgent("Mozilla/5.0 (Windows; U; WindowsNT 5.1; en-US; rv1.8.1.6) Gecko/20070725 Firefox/2.0.0.6")
.referrer("http://www.google.com")
.get();
if (document != null) {
Elements element = document.getElementsContainingOwnText("Current Version");
for (Element ele : element) {
if (ele.siblingElements() != null) {
Elements sibElemets = ele.siblingElements();
for (Element sibElemet : sibElemets) {
newVersion = sibElemet.text();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return newVersion;
}
#Override
protected void onPostExecute(String onlineVersion) {
super.onPostExecute(onlineVersion);
if (onlineVersion != null && !onlineVersion.isEmpty()) {
if (Float.valueOf(currentVersion) < Float.valueOf(onlineVersion)) {
//show anything
}
}
Log.d("update", "Current version " + currentVersion + "playstore version " + onlineVersion);
}
}
and don't forget to add JSoup library
dependencies {
compile 'org.jsoup:jsoup:1.8.3'}
and on Oncreate()
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String currentVersion;
try {
currentVersion = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
new GetVersionCode().execute();
}
that's it..
Thanks to this link
Coming From a Hybrid Application POV.
This is a javascript example, I have a Update Available footer on my main menu. If an update is available (ie. my version number within the config file is less than the version retrieved, display the footer) This will then direct the user to the app/play store, where the user can then click the update button.
I also get the whats new data (ie Release Notes) and display these in a modal on login if its the first time on this version.
On device Ready, set your store URL
if (device.platform == 'iOS')
storeURL = 'https://itunes.apple.com/lookup?bundleId=BUNDLEID';
else
storeURL = 'https://play.google.com/store/apps/details?id=BUNDLEID';
The Update Available method can be ran as often as you like. Mine is ran every time the user navigates to the home screen.
function isUpdateAvailable() {
if (device.platform == 'iOS') {
$.ajax(storeURL, {
type: "GET",
cache: false,
dataType: 'json'
}).done(function (data) {
isUpdateAvailable_iOS(data.results[0]);
}).fail(function (jqXHR, textStatus, errorThrown) {
commsErrorHandler(jqXHR, textStatus, false);
});
} else {
$.ajax(storeURL, {
type: "GET",
cache: false
}).done(function (data) {
isUpdateAvailable_Android(data);
}).fail(function (jqXHR, textStatus, errorThrown) {
commsErrorHandler(jqXHR, textStatus, false);
});
}
}
iOS Callback: Apple have an API, so very easy to get
function isUpdateAvailable_iOS (data) {
var storeVersion = data.version;
var releaseNotes = data.releaseNotes;
// Check store Version Against My App Version ('1.14.3' -> 1143)
var _storeV = parseInt(storeVersion.replace(/\./g, ''));
var _appV = parseInt(appVersion.substring(1).replace(/\./g, ''));
$('#ft-main-menu-btn').off();
if (_storeV > _appV) {
// Update Available
$('#ft-main-menu-btn').text('Update Available');
$('#ft-main-menu-btn').click(function () {
openStore();
});
} else {
$('#ft-main-menu-btn').html(' ');
// Release Notes
settings.updateReleaseNotes('v' + storeVersion, releaseNotes);
}
}
Android Callback: PlayStore you have to scrape, as you can see the version is relatively easy to grab and the whats new i take the html instead of the text as this way I can use their formatting (ie new lines etc)
function isUpdateAvailable_Android(data) {
var html = $(data);
var storeVersion = html.find('div[itemprop=softwareVersion]').text().trim();
var releaseNotes = html.find('.whatsnew')[0].innerHTML;
// Check store Version Against My App Version ('1.14.3' -> 1143)
var _storeV = parseInt(storeVersion.replace(/\./g, ''));
var _appV = parseInt(appVersion.substring(1).replace(/\./g, ''));
$('#ft-main-menu-btn').off();
if (_storeV > _appV) {
// Update Available
$('#ft-main-menu-btn').text('Update Available');
$('#ft-main-menu-btn').click(function () {
openStore();
});
} else {
$('#ft-main-menu-btn').html(' ');
// Release Notes
settings.updateReleaseNotes('v' + storeVersion, releaseNotes);
}
}
The open store logic is straight forward, but for completeness
function openStore() {
var url = 'https://itunes.apple.com/us/app/appname/idUniqueID';
if (device.platform != 'iOS')
url = 'https://play.google.com/store/apps/details?id=appid'
window.open(url, '_system')
}
Ensure Play Store and App Store have been Whitelisted:
<access origin="https://itunes.apple.com"/>
<access origin="https://play.google.com"/>
Firebase Remote Config is better.
Quickly and easily update our applications without the need to publish a new build to the app
Implementing Remote Config on Android
Adding the Remote Config dependancy
compile 'com.google.firebase:firebase-config:9.6.0'
Once done, we can then access the FirebaseRemoteConfig instance throughout our application where required:
FirebaseRemoteConfig firebaseRemoteConfig = FirebaseRemoteConfig.getInstance();
Retrieving Remote Config values
boolean someBoolean = firebaseRemoteConfig.getBoolean("some_boolean");
byte[] someArray = firebaseRemoteConfig.getByteArray("some_array");
double someDouble = firebaseRemoteConfig.getDouble("some_double");
long someLong = firebaseRemoteConfig.getLong("some_long");
String appVersion = firebaseRemoteConfig.getString("appVersion");
Fetch Server-Side values
firebaseRemoteConfig.fetch(cacheExpiration)
.addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
mFirebaseRemoteConfig.activateFetched();
// We got our config, let's do something with it!
if(appVersion < CurrentVersion){
//show update dialog
}
} else {
// Looks like there was a problem getting the config...
}
}
});
Now once uploaded the new version to playstore, we have to update the version number inside firebase. Now if it is new version the update dialog will display
Inside OnCreate method write below code..
VersionChecker versionChecker = new VersionChecker();
try {
latestVersion = versionChecker.execute().get();
Toast.makeText(getBaseContext(), latestVersion , Toast.LENGTH_SHORT).show();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
this gives you play store version of app..
then you have to check app version as below
PackageManager manager = getPackageManager();
PackageInfo info = null;
try {
info = manager.getPackageInfo(getPackageName(), 0);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
assert info != null;
version = info.versionName;
after that you can compare it with store version and setup your own update screens
if(version.equals(latestVersion)){
Toast.makeText(getBaseContext(), "No Update" , Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(getBaseContext(), "Update" , Toast.LENGTH_SHORT).show();
}
And add VersionChecker.class as below
public class VersionChecker extends AsyncTask<String, String, String> {
private String newVersion;
#Override
protected String doInBackground(String... params) {
try {
newVersion = Jsoup.connect("https://play.google.com/store/apps/details?id=" + "package name" + "&hl=en")
.timeout(30000)
.userAgent("Mozilla/5.0 (Windows; U; WindowsNT 5.1; en-US; rv1.8.1.6) Gecko/20070725 Firefox/2.0.0.6")
.referrer("http://www.google.com")
.get()
.select(".hAyfc .htlgb")
.get(7)
.ownText();
} catch (IOException e) {
e.printStackTrace();
}
return newVersion;
}
}
Set up a server that exposes an HTTP url that reports the latest version, then use an AlarmManager to call that URL and see if the version on the device is the same as the latest version. If it isn't pop up a message or notification and send them to the market to upgrade.
There are some code examples: How to allow users to check for the latest app version from inside the app?
Apart from using JSoup, we can alternatively do pattern matching for getting the app version from playStore.
To match the latest pattern from google playstore ie
<div class="BgcNfc">Current Version</div><span class="htlgb"><div><span class="htlgb">X.X.X</span></div>
we first have to match the above node sequence and then from above sequence get the version value. Below is the code snippet for same:
private String getAppVersion(String patternString, String inputString) {
try{
//Create a pattern
Pattern pattern = Pattern.compile(patternString);
if (null == pattern) {
return null;
}
//Match the pattern string in provided string
Matcher matcher = pattern.matcher(inputString);
if (null != matcher && matcher.find()) {
return matcher.group(1);
}
}catch (PatternSyntaxException ex) {
ex.printStackTrace();
}
return null;
}
private String getPlayStoreAppVersion(String appUrlString) {
final String currentVersion_PatternSeq = "<div[^>]*?>Current\\sVersion</div><span[^>]*?>(.*?)><div[^>]*?>(.*?)><span[^>]*?>(.*?)</span>";
final String appVersion_PatternSeq = "htlgb\">([^<]*)</s";
String playStoreAppVersion = null;
BufferedReader inReader = null;
URLConnection uc = null;
StringBuilder urlData = new StringBuilder();
final URL url = new URL(appUrlString);
uc = url.openConnection();
if(uc == null) {
return null;
}
uc.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; WindowsNT 5.1; en-US; rv1.8.1.6) Gecko/20070725 Firefox/2.0.0.6");
inReader = new BufferedReader(new InputStreamReader(uc.getInputStream()));
if (null != inReader) {
String str = "";
while ((str = inReader.readLine()) != null) {
urlData.append(str);
}
}
// Get the current version pattern sequence
String versionString = getAppVersion (currentVersion_PatternSeq, urlData.toString());
if(null == versionString){
return null;
}else{
// get version from "htlgb">X.X.X</span>
playStoreAppVersion = getAppVersion (appVersion_PatternSeq, versionString);
}
return playStoreAppVersion;
}
I got it solved through this, as this works for latest Google playstore changes also. Hope that helps.
private void CheckUPdate() {
VersionChecker versionChecker = new VersionChecker();
try
{ String appVersionName = BuildConfig.VERSION_NAME;
String mLatestVersionName = versionChecker.execute().get();
if(!appVersionName.equals(mLatestVersionName)){
AlertDialog.Builder alertDialog = new AlertDialog.Builder(Activity.this);
alertDialog.setTitle("Please update your app");
alertDialog.setMessage("This app version is no longer supported. Please update your app from the Play Store.");
alertDialog.setPositiveButton("UPDATE NOW", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
final String appPackageName = getPackageName();
try {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + appPackageName)));
} catch (android.content.ActivityNotFoundException anfe) {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=" + appPackageName)));
}
}
});
alertDialog.show();
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
#SuppressLint("StaticFieldLeak")
public class VersionChecker extends AsyncTask<String, String, String> {
private String newVersion;
#Override
protected String doInBackground(String... params) {
try {
newVersion = Jsoup.connect("https://play.google.com/store/apps/details?id="+getPackageName())
.timeout(30000)
.userAgent("Mozilla/5.0 (Windows; U; WindowsNT 5.1; en-US; rv1.8.1.6) Gecko/20070725 Firefox/2.0.0.6")
.referrer("http://www.google.com")
.get()
.select(".hAyfc .htlgb")
.get(7)
.ownText();
} catch (IOException e) {
e.printStackTrace();
}
return newVersion;
}
}
There is no official GooglePlay API to do it.
But you can use this unofficial library to get app version data.
And, if the above doesn't work for you, you can always http connect to your app's page (e.g. https://play.google.com/store/apps/details?id=com.shots.android&hl=en) and parse the "Current Version" field.
You can try following code using Jsoup
String latestVersion = doc.getElementsContainingOwnText("Current Version").parents().first().getAllElements().last().text();
Google introduced In-app updates feature, (https://developer.android.com/guide/app-bundle/in-app-updates) it works on Lollipop+ and gives you the ability to ask the user for an update with a nice dialog (FLEXIBLE) or with mandatory full-screen message (IMMEDIATE).
Here is how Flexible update will look like:
and here is Immedtiate update flow:
You can check my answer here https://stackoverflow.com/a/56808529/5502121 to get the complete sample code of implementing both Flexible and Immediate update flows.
Hope it helps!
confirmed only that method work now:
newVersion = Jsoup.connect("https://play.google.com/store/apps/details?id=" + AcMainPage.this.getPackageName() + "&hl=it")
.timeout(30000)
.userAgent("Mozilla/5.0 (Windows; U; WindowsNT 5.1; en-US; rv1.8.1.6) Gecko/20070725 Firefox/2.0.0.6")
.referrer("http://www.google.com")
.get()
.select(".hAyfc .htlgb")
.get(5)
.ownText();
Google introduced in-app update api. Using that we can ask user to update app inside the application. if user accept we can directly download latest app and install without redirect to playstore. for more details please refer the below link
link1link2
I am not sure about JAVA programming though, but with latest changes you can
Request https://play.google.com/store/apps/details?id=<package.name> url.
Parse the data as text.
Match regex /key: 'ds:4',\n[\ ]*hash: '[0-9]*',\n[\ ]*data:\ ([\S".,\[\]\ ]*),/ with the response.
This has a group with it which will give group value as ["size", "version", "supported android version"]. e.g. ["16M", "1.0.0", "5.0 and up"].
Parse the group as an array and you will get array[1] as latest version.
Implementation doesn't depend on any language.
if your app is on Google Play Store then Just use this function its automatically checks the app update and shows a msg to the user for update
public void checkUpdate()
{
if (isInternetOn())
{
AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(getApplicationContext());
Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE))
{
try {
appUpdateManager.startUpdateFlowForResult(appUpdateInfo, AppUpdateType.FLEXIBLE, this, "Your Request Code");
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
}
});
}
}
and additionally you can check internet Connectivity before checking the update with this function`
public boolean isInternetOn()
{
ConnectivityManager connec = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
if (connec.getNetworkInfo(0).getState() == android.net.NetworkInfo.State.CONNECTED ||
connec.getNetworkInfo(0).getState() == android.net.NetworkInfo.State.CONNECTING ||
connec.getNetworkInfo(1).getState() == android.net.NetworkInfo.State.CONNECTING ||
connec.getNetworkInfo(1).getState() == android.net.NetworkInfo.State.CONNECTED)
{
return true;
} else if (connec.getNetworkInfo(0).getState() == android.net.NetworkInfo.State.DISCONNECTED ||
connec.getNetworkInfo(1).getState() == android.net.NetworkInfo.State.DISCONNECTED)
{
return false;
}
return false;
}`
I've got an Android app which scans for all Apps installed on the device and then reports this to a server (it's an MDM agent). Any suggestions on how to get the Category of the App? Everyone has a different list of Categories, but basically something like Game, Entertainment, Tools/Utilities, etc.
From what I can tell there is nothing related to Category stored on the device itself. I was thinking of using the android market API to search for the application in the market and use the Category value returned by the search. Not sure how successful this will be finding a match. Any suggestions on how best to do this?
Any suggestions on a different approach?
Thanks in advance.
mike
I know that this is an old post, but for anyone still looking for this, API level 26 (O) has added categories to android.content.pm.ApplicationInfo.
From the docs https://developer.android.com/reference/android/content/pm/ApplicationInfo#category:
public int category
The category of this app. Categories are used to cluster multiple apps together into meaningful groups, such as when summarizing battery, network, or disk usage. Apps should only define this value when they fit well into one of the specific categories.
Set from the R.attr.appCategory attribute in the manifest. If the manifest doesn't define a category, this value may have been provided by the installer via PackageManager#setApplicationCategoryHint(String, int).
Value is CATEGORY_UNDEFINED, CATEGORY_GAME, CATEGORY_AUDIO, CATEGORY_VIDEO, CATEGORY_IMAGE, CATEGORY_SOCIAL, CATEGORY_NEWS, CATEGORY_MAPS, or CATEGORY_PRODUCTIVITY
One can now do something like:
PackageManager pm = context.getPackageManager();
ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName, 0);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
int appCategory = applicationInfo.category;
String categoryTitle = (String) ApplicationInfo.getCategoryTitle(context, appCategory)
// ...
}
if you get for each application its package name, you could ask directly to play store which category an app belongs, parsing html response page with this library:
org.jsoup.jsoup1.8.3
Here's a snippet to solve your problem:
public class MainActivity extends AppCompatActivity {
public final static String GOOGLE_URL = "https://play.google.com/store/apps/details?id=";
public static final String ERROR = "error";
...
private class FetchCategoryTask extends AsyncTask<Void, Void, Void> {
private final String TAG = FetchCategoryTask.class.getSimpleName();
private PackageManager pm;
private ActivityUtil mActivityUtil;
#Override
protected Void doInBackground(Void... errors) {
String category;
pm = getPackageManager();
List<ApplicationInfo> packages = pm.getInstalledApplications(PackageManager.GET_META_DATA);
Iterator<ApplicationInfo> iterator = packages.iterator();
while (iterator.hasNext()) {
ApplicationInfo packageInfo = iterator.next();
String query_url = GOOGLE_URL + packageInfo.packageName;
Log.i(TAG, query_url);
category = getCategory(query_url);
// store category or do something else
}
return null;
}
private String getCategory(String query_url) {
boolean network = mActivityUtil.isNetworkAvailable();
if (!network) {
//manage connectivity lost
return ERROR;
} else {
try {
Document doc = Jsoup.connect(query_url).get();
Element link = doc.select("span[itemprop=genre]").first();
return link.text();
} catch (Exception e) {
return ERROR;
}
}
}
}
}
You could make these queries in an AsyncTask, or in a service. Hope that you find it helpful.
I also faced the same issue. The solution for the above query is stated below.
Firstly, download the Jsoup library or download the jar file.
or
Add this to your build.gradle(Module: app) implementation 'org.jsoup:jsoup:1.11.3'
private class FetchCategoryTask extends AsyncTask<Void, Void, Void> {
private final String TAG = FetchCategoryTask.class.getSimpleName();
private PackageManager pm;
//private ActivityUtil mActivityUtil;
#Override
protected Void doInBackground(Void... errors) {
String category;
pm = getPackageManager();
List<ApplicationInfo> packages = pm.getInstalledApplications(PackageManager.GET_META_DATA);
Iterator<ApplicationInfo> iterator = packages.iterator();
// while (iterator.hasNext()) {
// ApplicationInfo packageInfo = iterator.next();
String query_url = "https://play.google.com/store/apps/details?id=com.imo.android.imoim"; //GOOGLE_URL + packageInfo.packageName;
Log.i(TAG, query_url);
category = getCategory(query_url);
Log.e("CATEGORY", category);
// store category or do something else
//}
return null;
}
private String getCategory(String query_url) {
try {
Document doc = Jsoup.connect(query_url).get();
Elements link = doc.select("a[class=\"hrTbp R8zArc\"]");
return link.text();
} catch (Exception e) {
Log.e("DOc", e.toString());
}
}
}
In return, you will get Application Company Name and category of the application
I made a Kotlin solution based on the answer from #Ankit Kumar Singh.
This solution maps the category to an enum, in case you want to do other things than just show it.
import kotlinx.coroutines.*
import org.jsoup.Jsoup
import javax.inject.Inject
import javax.inject.Singleton
class AppCategoryService {
companion object {
private const val APP_URL = "https://play.google.com/store/apps/details?id="
private const val CAT_SIZE = 9
private const val CATEGORY_STRING = "category/"
}
suspend fun fetchCategory(packageName: String): AppCategory {
val url = "$APP_URL$packageName&hl=en" //https://play.google.com/store/apps/details?id=com.example.app&hl=en
val categoryRaw = parseAndExtractCategory(url) ?: return AppCategory.OTHER
return AppCategory.fromCategoryName(categoryRaw)
}
#Suppress("BlockingMethodInNonBlockingContext")
private suspend fun parseAndExtractCategory(url: String): String? = withContext(Dispatchers.IO) {
return#withContext try {
val text = Jsoup.connect(url).get()?.select("a[itemprop=genre]") ?: return#withContext null
val href = text.attr("abs:href")
if (href != null && href.length > 4 && href.contains(CATEGORY_STRING)) {
getCategoryTypeByHref(href)
} else {
null
}
} catch (e: Throwable) {
null
}
}
private fun getCategoryTypeByHref(href: String) = href.substring(href.indexOf(CATEGORY_STRING) + CAT_SIZE, href.length)
}
And here is the enum with all the possible values at of this moment in time:
// Note: Enum name matches API value and should not be changed
enum class AppCategory {
OTHER,
ART_AND_DESIGN,
AUTO_AND_VEHICLES,
BEAUTY,
BOOKS_AND_REFERENCE,
BUSINESS,
COMICS,
COMMUNICATION,
DATING,
EDUCATION,
ENTERTAINMENT,
EVENTS,
FINANCE,
FOOD_AND_DRINK,
HEALTH_AND_FITNESS,
HOUSE_AND_HOME,
LIBRARIES_AND_DEMO,
LIFESTYLE,
MAPS_AND_NAVIGATION,
MEDICAL,
MUSIC_AND_AUDIO,
NEWS_AND_MAGAZINES,
PARENTING,
PERSONALIZATION,
PHOTOGRAPHY,
PRODUCTIVITY,
SHOPPING,
SOCIAL,
SPORTS,
TOOLS,
TRAVEL_AND_LOCAL,
VIDEO_PLAYERS,
WEATHER,
GAMES;
companion object {
private val map = values().associateBy(AppCategory::name)
private const val CATEGORY_GAME_STRING = "GAME_" // All games start with this prefix
fun fromCategoryName(name: String): AppCategory {
if (name.contains(CATEGORY_GAME_STRING)) return GAMES
return map[name.toUpperCase(Locale.ROOT)] ?: OTHER
}
}
}
private fun getCategory(){
val GOOGLE_URL = "https://play.google.com/store/apps/details?id=com.google.android.deskclock"
lifecycleScope.launch(Dispatchers.IO) {
val doc: Document = Jsoup.connect(GOOGLE_URL).get()
val index = doc.body().data().indexOf("applicationCategory")
val simpleString = doc.body().data().subSequence(index,index+100)
val data = simpleString.split(":")[1].split(",")[0]
Log.e("DATA-->",data.toString())
}
}
You can use below AsyncTask for extract Android app category from playStore by using app package id.
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.util.Log;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.IOException;
public class GetAppCategory extends AsyncTask<String, Void, String> {
//Main URL for each app on Play Store
public static final String APP_URL = "https://play.google.com/store/apps/details?id=";
//Use below String if extracting 'CATEGORY' from href tag.
private final String CATEGORY_STRING = "category/";
private final int cat_size = 9;
/*Use below String for identified 'GAME' apps, which must start with this prefix.
Here I only display 'Games' as category for all Games app instead of showing their subCategory also*/
private final String CATEGORY_GAME_STRING = "GAME_";
//Name of the app package for which you want to get category.
private String packageName = null;
private PackageManager pm = null;
//Activity or Application context as per requirement.
private Context appContext;
/* You can add default system app OR non play store app package name here as comma seprated for ignore them
and set their category directly 'Others' OR anythings you wish. */
private final String extractionApps = "com.android.providers.downloads.ui, com.android.contacts," +
" com.android.gallery3d, com.android.vending, com.android.calculator2, com.android.calculator," +
" com.android.deskclock, com.android.messaging, com.android.settings, com.android.stk";
//Class level TAG, use for Logging.
private final String TAG = "GetAppCategory";
/**
* #param packageName: package name of the app, you want to extract category.
* #param appContext: Activity/Application level Context ap per requirement.
*/
public GetAppCategory(String packageName, Context appContext) {
this.packageName = packageName;
this.appContext = appContext;
}
#Override
protected String doInBackground(String... params) {
try {
pm = appContext.getPackageManager();
if (packageName != null && packageName.length() > 1) {
if (packageName.contains("package:")) {
packageName = packageName.replace("package:", "");
}
/**
* Mathod used for parse play store html page and extract category from their.
*/
String appCategoryType = parseAndExtractCategory(packageName);
Log.i(TAG, "package :" + packageName);
Log.i(TAG, "APP_CATEGORY: " + appCategoryType);
}
} catch (Exception e) {
//TODO:: Handle Exception
e.printStackTrace();
} finally {
//TODO::
}
return null;
}
#Override
protected void onPostExecute(String result) {
}
/**
* #param packageName
* #return
*/
private String parseAndExtractCategory(String packageName) {
//You can pass hl={language_code} for get category in some other langauage also other than English.
//String url = APP_URL + packageName + "&hl=" + appContext.getString(R.string.app_lang);
String url = APP_URL + packageName + "&hl=en"; //{https://play.google.com/store/apps/details?id=com.example.app&hl=en}
String appCategoryType = null;
String appName = null;
try {
if (!extractionApps.contains(packageName)) {
Document doc = null;
try {
doc = Jsoup.connect(url).get();
if (doc != null) {
//TODO: START_METHOD_1
//Extract category String from a <anchor> tag value directly.
//NOTE: its return sub category text, for apps with multiple sub category.
//Comment this method {METHOD_1}, if you wish to extract category by href value.
Element CATEGORY_SUB_CATEGORY = doc.select("a[itemprop=genre]").first();
if (CATEGORY_SUB_CATEGORY != null) {
appCategoryType = CATEGORY_SUB_CATEGORY.text();
}
//TODO: END_METHOD_1
//TODO: START_METHOD_2
// Use below code only if you wist to extract category by href value.
//Its return parent or Main Category Text for all app.
//Comment this method {METHOD_2}, If you wihs to extract category from a<anchor> value.
if (appCategoryType == null || appCategoryType.length() < 1) {
Elements text = doc.select("a[itemprop=genre]");
if (text != null) {
if (appCategoryType == null || appCategoryType.length() < 2) {
String href = text.attr("abs:href");
if (href != null && href.length() > 4 && href.contains(CATEGORY_STRING)) {
appCategoryType = getCategoryTypeByHref(href);
}
}
}
}
//TODO: END_METHOD_2
if (appCategoryType != null && appCategoryType.length() > 1) {
/**
* Ger formatted category String by removing special character.
*/
appCategoryType = replaceSpecialCharacter(appCategoryType);
}
}
} catch (IOException e) {
//appCategoryType = appContext.getString(R.string.category_others);
appCategoryType = "OTHERS";
//TODO:: Handle Exception
e.printStackTrace();
}
} else {
//appCategoryType = appContext.getString(R.string.category_others);
appCategoryType = "OTHERS";
}
} catch (Exception e) {
//TODO:: Handle Exception
e.printStackTrace();
}
return appCategoryType;
}
/**
* #param href
* #return
*/
private String getCategoryTypeByHref(String href) {
String appCategoryType = null;
try {
appCategoryType = href.substring((href.indexOf(CATEGORY_STRING) + cat_size), href.length());
if (appCategoryType != null && appCategoryType.length() > 1) {
if (appCategoryType.contains(CATEGORY_GAME_STRING)) {
//appCategoryType = appContext.getString(R.string.games);
appCategoryType = "GAMES";
}
}
} catch (Exception e) {
//TODO:: Handle Exception
e.printStackTrace();
}
return appCategoryType;
}
/**
* #param appCategoryType
* #return: formatted String
*/
private String replaceSpecialCharacter(String appCategoryType) {
try {
//Find and Replace '&' with '&' in category Text
if (appCategoryType.contains("&")) {
appCategoryType = appCategoryType.replace("&", " & ");
}
//Find and Replace '_AND_' with ' & ' in category Text
if (appCategoryType.contains("_AND_")) {
appCategoryType = appCategoryType.replace("_AND_", " & ");
}
//Find and Replace '_' with ' ' <space> in category Text
if (appCategoryType.contains("_")) {
appCategoryType = appCategoryType.replace("_", " ");
}
} catch (Exception e) {
//TODO:: Handle Exception
e.printStackTrace();
}
return appCategoryType;
}
}
It's requires jsoup library for parsing the html page. you can find it here org.jsoup.jsoup1.11.1
Probably a bit late, but the problem is still here.
The OP has the advantage because of sending those results to the API (here I assume that the API is managed by the OP or his API colleagues at least).
So, for anyone with the similar problem I'd suggest following:
Collect all the package names you're interested in from device.
Send that data to the your API
API should extract package names and try to read results from its cache / db...
For those packages that do not exist in cache / db make "market API" call and extract category - save it to the db / cache for reuse in this iteration.
When all requests (to cache / db and market API) are completed do whatever you like with the results.
Things to consider:
When multiple users try to query your API for a same package name and you don't have a category for that package in your cache / db...
Do 1 request to "market API" for packagex and update packagex in your cache / db to "waiting for results" state - next request should either get a "waiting for results" or a result that "market API" returned.
One should also consider a fallback for possible "market API" fails (market API not working, not a google play app, or something similar). This decision is basically tied to your domain and the business trend that you're trying to catch will force a decision about this for you. If you're really into getting this category stuff sorted out you could pipeline this fallback to human decision and update your API db / cache for packagex accordingly.
put up a nice API that would handle these and similar scenarios gracefully then one could probably even commercialize it up to a certain extent and "market API endpoint" - AKA play store package details page. That page would lose a big part of it's fake users :)