How to get Category for each App on device on Android?
Asked Answered
B

7

12

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

Bronchial answered 22/5, 2012 at 21:26 Comment(1)
I think I would do it. I used the (unofficial) Android "market" API and in my opinion its very Developer friendly and reliable. Best way to search is to search for packagename and you can get the category in return. I can't think of an other way, because in the App itself there isnt defined wheater its a game or a buisiness app etc. So good luck with that!Bushhammer
M
13

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)
    // ...
}
Megohm answered 14/5, 2020 at 15:28 Comment(0)
I
10

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.

Inartistic answered 9/3, 2016 at 21:39 Comment(3)
Hey, it works well, but what if google play is in other language? How to get only english version of category?Uranography
try to add "&&hl=en" to query_url ;)Nab
I tried this code and it always returning error. Is there anything I want to do apart from this code ?Citizenry
C
5

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
        }
    }
}
Carlcarla answered 24/9, 2018 at 17:42 Comment(0)
O
4

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

Oleneolenka answered 18/12, 2018 at 3:24 Comment(3)
from above solution i got both company name and category name but Is it possible to find its a game or app?Wakefield
@Wakefield yes you can find whether it is an game or application. Games also have sub-category list it down(eg. Action, Adventure, Arcade,etc). if my application category is same as Action or Adventure or Arcade I'll assign it as a GAME It's a work around but this might help you discover whether application is game or not ThanskOleneolenka
Will this work even today? Will it always be in English? Is there a way to get all possible categories? In case things change, how did you find what to put as parameter for doc.select() ?Romanaromanas
S
1

 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())
        }

    }
Survance answered 14/6, 2022 at 11:34 Comment(0)
E
0

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 '&amp;' with '&' in category Text
            if (appCategoryType.contains("&amp;")) {
                appCategoryType = appCategoryType.replace("&amp;", " & ");
            }

            //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

Enshrine answered 29/5, 2018 at 12:7 Comment(0)
B
0

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:

  1. Collect all the package names you're interested in from device.
  2. Send that data to the your API
  3. API should extract package names and try to read results from its cache / db...
  4. 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.
  5. 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 :)

Bogeyman answered 29/10, 2018 at 21:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.