Google playstore app version check not working any more - android

For force update I used to call the app url and check the for the html tag value <softwareVersion><softwareVersion/> but suddenly it stopped working there is no softwareVersion tag in the page so getting null. Is there any google api available to check the google play app version.
Update -
I investigate in more details when I used to call the url the response was 200 but now I am getting 405

I come up with a solution. When ever I am pushing a new version in the playstore I will add the version in the what's new, like this -
WHAT'S NEW
Version - 2.11.0
- New changes 1
- New changes 2
And I look for this Version -
So Full code looks like this -
class VersionCheckTask extends AsyncTask<String, Void, String> {
protected String doInBackground(String... urls) {
String mVer = "";
String mData = "";
try {
URL mUrl = new URL(urls[0]);
BufferedReader in = new BufferedReader(new InputStreamReader(mUrl.openStream()));
String inputLine;
while ((inputLine = in.readLine()) != null){
if (inputLine == null)
break;
mData += inputLine;
}
in.close();
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
/*
* we are looking for this tag <div itemprop="description"><content>Version - 2.11.0<br>
* We need to make sure every time we release a new version we should add the line in what's new -
*
* Version - 2.11.1
*
* - New changes 1
* - New changes 2
*/
String startToken = "Version - ";
String endToken = "<";
int index = mData.indexOf(startToken);
if (index == -1) {
mVer = null;
} else {
mVer = mData.substring(index + startToken.length(), index
+ startToken.length() + 100);
mVer = mVer.substring(0, mVer.indexOf(endToken)).trim();
}
return mVer;
}
protected void onPostExecute(String store_version) {
String currentVersion = "";
try {
PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
currentVersion = pInfo.versionName;
} catch (PackageManager.NameNotFoundException e) {
Log.v(TAG, "Recv NameNotFoundException. Msg:" + e.getMessage());
}
Log.d(TAG, "store_version: " + store_version);
Log.d(TAG, "device_version: " + currentVersion);
if (store_version != null) {
if (versionCompare(store_version, currentVersion) > 0) {
dialog.setMessage(String.format(getResources().getString(R.string.update_message), getResources().getString(R.string.app_name), store_version));
dialog.show();
} else {
showDisclaimer();
}
}
}
}
public int versionCompare(String storeVersion, String currentVersion) {
String[] vals1 = storeVersion.split("\\.");
String[] vals2 = currentVersion.split("\\.");
int i = 0;
// set index to first non-equal ordinal or length of shortest version string
while (i < vals1.length && i < vals2.length && vals1[i].equals(vals2[i])) {
i++;
}
// compare first non-equal ordinal number
if (i < vals1.length && i < vals2.length) {
int diff = Integer.valueOf(vals1[i]).compareTo(Integer.valueOf(vals2[i]));
return Integer.signum(diff);
}
// the strings are equal or one string is a substring of the other
// e.g. "1.2.3" = "1.2.3" or "1.2.3" < "1.2.3.4"
return Integer.signum(vals1.length - vals2.length);
}

Maybe, at the end of march, Google changed Play store's HTML Code.
The structure of additional information has also changed.
Some developers, including me, use Jsoup to check for the latest version in the Play Store.
Perhaps you were using code like this:
Document doc = Jsoup.connect
("https://play.google.com/store/apps/details?id=name.package.your").get();
Elements Version = doc.select(".content");
for (Element v : Version) {
if (v.attr("itemprop").equals("softwareVersion")) {
VersionMarket = v.text();
}
}
but, after play store's change, your code return null.
because, "itemprop" and "sofrwareVersion" is gone, like that.
enter image description here
So, you need a new way to parse the version of your app in Google Play store's ADDITION INFORMATION with Jsoup.
try {
Document doc = Jsoup
.connect(
"https://play.google.com/store/apps/details?id=name.package.your")
.get();
Elements Version = doc.select(".htlgb ");
for (int i = 0; i < 5 ; i++) {
VersionMarket = Version.get(i).text();
if (Pattern.matches("^[0-9]{1}.[0-9]{1}.[0-9]{1}$", VersionMarket)) {
break;
}
}
The above code works as follows.
Parsing play store's your app page.
Selecting all "htlgb" contents.
like in image, "3 March 2018", "1,000+" "2.0.4", "4.4 and up, etc."
In [for Loop], [Regex] finds a value matching your version pattern (like 2.0.4) and stops.
VersionMarket is your "app version" and you can use it.
//2018-08-04 Add Comment
For some reason, the code above returns information about "Installs" instead of "version information".
Therefore, if you modify the code as shown below, you can return "version information" again.
try {
Document doc = Jsoup
.connect(
"https://play.google.com/store/apps/details?id=name.package.your")
.get();
Elements Version = doc.select(".htlgb ");
for (int i = 0; i < 10 ; i++) {
VersionMarket = Version.get(i).text();
if (Pattern.matches("^[0-9]{1}.[0-9]{1}.[0-9]{1}$", VersionMarket)) {
break;
}
}
The above code changed the number in "for break" from 5 to 10.
Because the number of "htlgb" codes has changed in Google Play Store's HTML Code.

No, no API exists for checking your app version on Play. Instead, you could implement a solution using Firebase Remote Config, then you have much more control over the minimum version your users see.

Related

React Native: Zoom Android SDK: "startMeetingWithParams" starts a meeting as a participant - need it to start a meeting as host

"startMeetingWithParams" starts a meeting as a participant - need it to start a meeting as host:
I'm trying to create a simple app with the react-native-zoom-us open source project which is claimed to be acting as a minimum bridge to the "zoom android sdk". The zoom android sdk is being accessed in this bridge through importing "us.zoom.sdk.ZoomSDK". The basic functionality provided by it includes startMeetingWithParams() function of the MeetingService of the android sdk. In the code (attached below), I can see that they are setting StartMeetingParamsWithoutLogin() with certain params and then passing it as an argument to startMeetingWithParams(). This is starting a meeting with user as a participant (even when host_id of the meeting is set in the parameter "userId") and the default waiting room screen shows up waiting for the host to start the meeting, whereas it is expected to start the meeting as the host. Please give your inferences on this and let me know what needs to be done to solve this. And does something have to be done with the StartMeetingOptions? Also what is the userType parameter here? It is said to be 'integer: 2' for a pro user. However, I'm not sure what type of a zoom user I am. Kindly let me know how I can know about the userType.
#ReactMethod
public void startMeeting(
final String displayName,
final String meetingNo,
final String userId,
final int userType,
final String zoomAccessToken,
final String zoomToken,
Promise promise
) {
try {
meetingPromise = promise;
ZoomSDK zoomSDK = ZoomSDK.getInstance();
if(!zoomSDK.isInitialized()) {
promise.reject("ERR_ZOOM_START", "ZoomSDK has not been initialized successfully");
return;
}
final MeetingService meetingService = zoomSDK.getMeetingService();
if(meetingService.getMeetingStatus() != MeetingStatus.MEETING_STATUS_IDLE) {
long lMeetingNo = 0;
try {
lMeetingNo = Long.parseLong(meetingNo);
} catch (NumberFormatException e) {
promise.reject("ERR_ZOOM_START", "Invalid meeting number: " + meetingNo);
return;
}
if(meetingService.getCurrentRtcMeetingNumber() == lMeetingNo) {
meetingService.returnToMeeting(reactContext.getCurrentActivity());
promise.resolve("Already joined zoom meeting");
return;
}
}
StartMeetingOptions opts = new StartMeetingOptions();
StartMeetingParamsWithoutLogin params = new StartMeetingParamsWithoutLogin();
params.displayName = displayName;
params.meetingNo = meetingNo;
params.userId = userId;
params.userType = userType;
params.zoomAccessToken = zoomAccessToken;
// params.zoomToken = zoomToken;
int startMeetingResult = meetingService.startMeetingWithParams(reactContext.getCurrentActivity(), params, opts);
Log.i(TAG, "startMeeting, startMeetingResult=" + startMeetingResult);
if (startMeetingResult != MeetingError.MEETING_ERROR_SUCCESS) {
promise.reject("ERR_ZOOM_START", "startMeeting, errorCode=" + startMeetingResult);
}
} catch (Exception ex) {
promise.reject("ERR_UNEXPECTED_EXCEPTION", ex);
}
}
To work with api, you need to upgrade your subscription to paid user. To check that you should be able to host meetings greater than 40 minutes.
According to the ZOOM Android SDK reference here https://marketplace.zoom.us/docs/sdk/sdk-reference/android-reference:
and after taking look at the SDK source code, these are the values for userTypes:
int USER_TYPE_API_USER = 0;
int USER_TYPE_ZOOM = 1;
int USER_TYPE_FACEBOOK = 2;
int USER_TYPE_GOOGLE_OAUTH = 3;
int USER_TYPE_SSO = 4;
int USER_TYPE_UNKNOWN = -1;
As you can see, it references the way, how user was logged in.
I decided to use USER_TYPE_API_USER, as I needed to download Zoom Access Token (via using Zoom API).
I also created tutorial about inegrating Zoom SDK into React Native here:
https://stefanmajiros.medium.com/how-to-integrate-zoom-sdk-into-react-native-47492d4e46a6

PackageManager getChangedPackages always return NULL

In my app I need to monitorize recently added or updated packages, but since Oreo this is a hard task.
To do it I have a service that runs every X time to detect the new installed/updated apps.
The main core of this service is to call the getChangedPackages function from the PackageManager, but this function always returns null, even if I install or update any app from or not from the Play Store in the interval between two consequtive calls to getChangedPackages.
https://developer.android.com/reference/android/content/pm/PackageManager.html#getChangedPackages(int)
I need to request any permission to call this function? Is the getChangedPackages buggy?
private void _doProcess()
{
try
{
PackageManager package_manager = getPackageManager();
int sequence_number = ApplicationPreferences.getInteger(this, GET_CHANGED_PACKAGES_SEQUENCE_NUMBER_KEY, 0);
ChangedPackages changed_packages = package_manager.getChangedPackages(sequence_number);
LogUtilities.show(this, String.format("Retrieve recently apps installs/updates using sequence number %d returns %s", sequence_number, changed_packages == null ? "null" : "a not null object"));
if (changed_packages == null) changed_packages = package_manager.getChangedPackages(0);
LogUtilities.show(this, String.format("Retrieve recently apps installs/updates using sequence number %d returns %s", sequence_number, changed_packages == null ? "null" : "a not null object"));
if (changed_packages != null)
{
List<String> packages_names = changed_packages.getPackageNames();
LogUtilities.show(this, String.format("%d recently installed/updated apps", packages_names == null ? 0 : packages_names.size()));
if (packages_names != null) for (String package_name : packages_names) PackagesUpdatedReceiver.doProcessPackageUpdate(this, new Intent(isNewInstall(package_manager, package_name) ? Intent.ACTION_PACKAGE_ADDED : Intent.ACTION_PACKAGE_REPLACED).setData(Uri.parse(String.format("package:%s", package_name))));
LogUtilities.show(this, String.format("Storing %s is the sequence number for next iteration", changed_packages.getSequenceNumber()));
ApplicationPreferences.putInteger(this, GET_CHANGED_PACKAGES_SEQUENCE_NUMBER_KEY, changed_packages.getSequenceNumber());
}
else
{
LogUtilities.show(this, String.format("Storing %s is the sequence number for next iteration", sequence_number + 1));
ApplicationPreferences.putInteger(this, GET_CHANGED_PACKAGES_SEQUENCE_NUMBER_KEY, sequence_number + 1);
}
}
catch (Exception e)
{
LogUtilities.show(this, e);
}
}
My experimental results so far have shown that this PackageManager API method getChangedPackages() is not reliable: quite often the returned ChangedPackages value contains many unchanged packages. So I’ve decided to implement a similar feature in a class called PackageUtils, as shown below. The idea is to poll for all the installed packages, as shown in method getInstalledPackageNames() below, and compare the string list with a previously saved one. This comparison boils down to comparing 2 string lists, as shown in method operate2StringLists() below. To get a set of removed packages, use GET_1_MINUS_2_OR_REMOVED as operation. To get a set of added packages, use GET_2_MINUS_1_OR_ADDED as operation.
public class PackageUtils {
public static final int GET_1_MINUS_2_OR_REMOVED = 0;
public static final int GET_2_MINUS_1_OR_ADDED = 1;
// Get all the installed package names
public static List<String> getInstalledPackageNames(Context context) {
List<String> installedPackageNames = new ArrayList<>();
try {
PackageManager packageManager = context.getPackageManager();
List<ApplicationInfo> appInfoList = packageManager.getInstalledApplications(PackageManager.GET_META_DATA);
for (ApplicationInfo appInfo : appInfoList) {
installedPackageNames.add(appInfo.packageName);
}
} catch (Exception e) {
e.printStackTrace();
}
return installedPackageNames;
}
// Compare 2 string lists and return differences.
public static Set<String> operate2StringLists(List<String> pkgList1, List<String> pkgList2, int operation) {
Set<String> result = null;
Set<String> pkgSet1 = new HashSet<String>(pkgList1);
Set<String> pkgSet2 = new HashSet<String>(pkgList2);
switch (operation) {
case GET_1_MINUS_2_OR_REMOVED:
pkgSet1.removeAll(pkgSet2);
result = pkgSet1;
break;
case GET_2_MINUS_1_OR_ADDED:
pkgSet2.removeAll(pkgSet1);
result = pkgSet2;
break;
default:
break;
}
return result;
}
}
The code has been tested on an Android Oreo device. It can reliably detect all added and removed packages between 2 time instances. However, it can’t detect updated packages in-between.
Finally got it. You have to create a variable called sequenceNumber, and update it every time you query changed packages.
private static int sequenceNumber = 0;
...
PackageManager pm = getContext().getPackageManager();
ChangedPackages changedPackages = pm.getChangedPackages(sequenceNumber);
if(changedPackages != null)
sequenceNumber = changedPackages.getSequenceNumber();

Read content of another app through my app

I have been searching the same problem for days. But unable to get any hint for that.
I need to create an app like voodoo app, which shows its custom layout only on specific pages of different apps like flipkart,etc.
Now, till this time, i have found options of using AccessebilityService and MediaProjection classes for the same. But i am stuck, how can i know programmatically, that Flipkart's Product Detail Page is visible so that i can display my app's custom view over it like Voodoo app does.
Any suggestions?
What you want to do is the following.
Using accessibility services track incoming events. Then you want to track TYPE_WINDOW_CONTENT_CHANGED events, and detect when the window content matches what you'd expect.
#Override
public void onAccessibilityEvent(AccessibilityEvent e) {
switch (e.getEventType()) {
case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: {
if (isFlipkartProdcutDetailPage(getRootInActiveWindow()) {
doStuff()
}
}
}
}
public boolean isFlipkartProductDetailPage(AccessibilityNodeInfo nodeInfo) {
//Use the node info tree to identify the proper content.
//For now we'll just log it to logcat.
Log.w("TAG", toStringHierarchy(nodeInfo, 0));
}
private String toStringHierarchy(AccessibilityNodeInfo info, int depth) {
if (info == null) return "";
String result = "|";
for (int i = 0; i < depth; i++) {
result += " ";
}
result += info.toString();
for (int i = 0; i < info.getChildCount(); i++) {
result += "\n" + toStringHierarchy(info.getChild(i), depth + 1);
}
return result;
}

Neither Fabric for Android nor twitter4j find tweets containing a specific user's mention

I'm trying to retrieve tweets that mention an specific user either using fabric for android or twitter4j, but I'm not getting anything.
-With Fabric for Android I'm doing it with "twitterApiClient.getSearchService().tweets" (logged in as a guest previously)
-With twitter4j, this is my code:
public List<Long> twList (String user){
List<Long> listTw = new ArrayList<>();
lastID = Long.MAX_VALUE;
int tamano = 0;
ArrayList<Status> tweets = new ArrayList<Status>();
String mention = "#"+user.trim();
query = new Query(user);
while (tweets.size() < numberOfTweets) {
tamano = tweets.size();
if (numberOfTweets - tweets.size() > 100) {
query.setCount(100);
} else {
query.setCount(numberOfTweets - tweets.size());
}
try {
QueryResult result = twitter.search(query);
tweets.addAll(result.getTweets());
Log.d("App", "Gathered " + tweets.size() + " tweets");
for (Status t : tweets) {
if (t.getId() < lastID) {
lastID = t.getId();
}
}
} catch (TwitterException te) {
Log.d("App", "Couldn't connect: " + te);
}
query.setMaxId(lastID - 1);
if (tamano == tweets.size()) break;
}
for (Status s: tweets){
if (s.getText().matches(mention)){
listTw.add(s.getId());
Log.d("App", s.getText());
}
}
tweets.clear();
return listTw;
}
When I look for the username on Twitter website, I get those tweets where it's mentioned.
Any idea?
When was the last time a Tweet including the username you're searching for, posted? The Search API (which is used by both Fabric and Twitter4j) only covers around 7 days of data. The website has the complete archive, but that is not available in the public Twitter API.

How to get Category for each App on device on Android?

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 :)

Categories

Resources