23

Background

I'm trying to check if an activity (or any other app component type, for that matter) is enabled/disabled at runtime.

The problem

It's possible to use the next code:

    final ComponentName componentName = new ComponentName(context, activityClass);
    final PackageManager pm = context.getPackageManager();
    final int result = pm.getComponentEnabledSetting(componentName);

But the returned result, as written on the documentation is:

Returns the current enabled state for the component. May be one of COMPONENT_ENABLED_STATE_ENABLED, COMPONENT_ENABLED_STATE_DISABLED, or COMPONENT_ENABLED_STATE_DEFAULT. The last one means the component's enabled state is based on the original information in the manifest as found in ComponentInfo.

So it's not just enabled/disabled, but also "default".

The question

If "COMPONENT_ENABLED_STATE_DEFAULT" is returned, how do I know if it's default as enabled or disabled (at runtime)?

The reason for this question is that the code should work no matter what people put in the manifest (for the "enabled" attribute) .

Is it possible perhaps to use intents resolving?

android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • What "any other app components" do you have in mind? Any of which are disabled by default? – Simas Nov 16 '14 at 11:57
  • @user3249477 The API states you can perform this check on other app components : "activity, receiver, service, provider" . Check this link: http://developer.android.com/reference/android/content/pm/PackageManager.html#getComponentEnabledSetting(android.content.ComponentName) – android developer Nov 16 '14 at 12:10
  • Did you ever find a solution to this? There is also the `enabled` field which appears to represent the manifest value. However if the encapsulating application is disabled then it modifies the field to false. – Cory Charlton Mar 30 '16 at 18:09
  • @CoryCharlton No. Maybe I should put a bounty? – android developer Mar 30 '16 at 19:23
  • Did you try `getActivityInfo()` and the like, calling [`ComponentInfo.isEnabled()`](http://developer.android.com/reference/android/content/pm/ComponentInfo.html#isEnabled()) on them? It says *Return whether this component and its enclosing application are enabled.* – David Medenjak Mar 30 '16 at 21:18
  • @DavidMedenjak, the "and its enclosing application" is part of the problem for me. I need to know if the component is enabled and do not care about the application's state. As I mentioned there is also an `enabled` field but it's not clear what this actually represents as sometimes it's true even when the component enabled setting is one of the two DISABLED states. Which is why I chose to use `getComponentEnabledSetting()` as it always represents what the `pm` command does but the `COMPONENT_ENABLED_STATE_DEFAULT` is ambiguous. – Cory Charlton Mar 30 '16 at 21:25

3 Answers3

17

If COMPONENT_ENABLED_STATE_DEFAULT is returned, how do I know if it's default as enabled or disabled?

You will need to load all the components using PackageManager and check the enabled state for the matching ComponentInfo. The following code should work:

public static boolean isComponentEnabled(PackageManager pm, String pkgName, String clsName) {
  ComponentName componentName = new ComponentName(pkgName, clsName);
  int componentEnabledSetting = pm.getComponentEnabledSetting(componentName);

  switch (componentEnabledSetting) {
    case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
      return false;
    case PackageManager.COMPONENT_ENABLED_STATE_ENABLED:
      return true;
    case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT:
    default:
      // We need to get the application info to get the component's default state
      try {
        PackageInfo packageInfo = pm.getPackageInfo(pkgName, PackageManager.GET_ACTIVITIES
            | PackageManager.GET_RECEIVERS
            | PackageManager.GET_SERVICES
            | PackageManager.GET_PROVIDERS
            | PackageManager.GET_DISABLED_COMPONENTS);

        List<ComponentInfo> components = new ArrayList<>();
        if (packageInfo.activities != null) Collections.addAll(components, packageInfo.activities);
        if (packageInfo.services != null) Collections.addAll(components, packageInfo.services);
        if (packageInfo.providers != null) Collections.addAll(components, packageInfo.providers);

        for (ComponentInfo componentInfo : components) {
          if (componentInfo.name.equals(clsName)) {
            return componentInfo.isEnabled();
          }
        }

        // the component is not declared in the AndroidManifest
        return false;
      } catch (PackageManager.NameNotFoundException e) {
        // the package isn't installed on the device
        return false;
      }
  }
}

Testing the above code on my device:

System.out.println(isComponentEnabled(getPackageManager(),
    "com.android.systemui",
    "com.android.systemui.DessertCaseDream"));

System.out.println(isComponentEnabled(getPackageManager(),
    "com.android.settings",
    "com.android.settings.DevelopmentSettings"));

false

true

Jared Rummler
  • 37,824
  • 19
  • 133
  • 148
  • This was close to the code I was previously using. The problem is that the `componentInfo.enabled` is not accurate. See the comment for the `enabled` field at https://android.googlesource.com/platform/frameworks/base/+/ee0b3e9/core/java/android/content/pm/ComponentInfo.java The problem for me is that when I disable the application the `enabled` field for some `ComponentInfo` objects gets set to false (`ServiceInfo` in particular). It's technically an accurate answer since the component can't run if the application is disabled but not to the question we are (or I am) asking. – Cory Charlton Mar 30 '16 at 22:18
  • @CoryCharlton it has been accurate for me. I have been using something very similar in an app with millions of users. Did you try it out? If you can reproduce the error then I'll look into it and update my answer. – Jared Rummler Mar 30 '16 at 22:26
  • Just tried your code and it returns true/true for "com.android.settings.DevelopmentSettings" but true/false for "com.android.settings.bluetooth.DockService". The second results are after executing `pm disable com.android.settings` or `pm disable-user com.android.settings` from a shell on a rooted Android 4.4.4, API 19 device. – Cory Charlton Mar 30 '16 at 23:08
  • @CoryCharlton You mean that it should also check if the app itself is disabled? – android developer Mar 31 '16 at 05:17
  • @androiddeveloper No I'm saying that in some cases the `enabled` field appears to take the application's enabled state into account and that is not the behavior I expect or want. – Cory Charlton Mar 31 '16 at 05:58
  • @CoryCharlton I don't understand how can it be, and how it can be fixed. Please explain and give examples of what you mean. – android developer Mar 31 '16 at 06:34
  • @CoryCharlton I edited the answer yesterday. Before it was using the public field `ComponentInfo.enabled`. Now it uses `ComponentInfo.isEnabled()` which also checks if the application has been frozen (disabled using `pm disable [PKG_NAME]`). It should work for you. – Jared Rummler Mar 31 '16 at 18:12
  • @CoryCharlton So I should mark this answer now? Does it cover all issues that you guys found? For now, I give you +1 for the effort. :) – android developer Apr 03 '16 at 09:25
  • @androiddeveloper if it works for you then accept it. My problem is that I want the behavior of `ComponentInfo.enabled` but sometimes that behaves like `ComponentInfo.isEnabled()`. The more I dig into it though this seems like a possible issue with my only rooted test device. – Cory Charlton Apr 03 '16 at 16:43
3

I think the field ComponentInfo.enabled means the value set in the AndroidManifest.xml file. If not specified, the default will be true. So, in the following example:

<receiver android:name=".BroadcastReceiver1"
            android:enabled="false" />

<receiver android:name=".BroadcastReceiver2"
            android:enabled="true" />

<receiver android:name=".BroadcastReceiver3" />

the enabled field will be false in BroadcastReceiver1, true in BroadcastReceiver2 and true in BroadcastReceiver3, while pm.getComponentEnabledSetting(componentName) will return ENABLED or DISABLED if the value in AndroidManifest is overridden or DEFAULT if not overriden

Then, according to @Jared Rummler answer if pm.getComponentEnabledSetting(componentName) returns PackageManager.COMPONENT_ENABLED_STATE_DEFAULT the actual value will be the one set in ComponentInfo.enabled field which will be the same as the one set in AndroidManifest, while ComponentInfo.isEnabled() would depend on the whole application state

EDIT: To sum up:

public static boolean isComponentEnabled(PackageManager pm, String pkgName, String clsName) {
  ComponentName componentName = new ComponentName(pkgName, clsName);
  int componentEnabledSetting = pm.getComponentEnabledSetting(componentName);

  switch (componentEnabledSetting) {
    case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
      return false;
    case PackageManager.COMPONENT_ENABLED_STATE_ENABLED:
      return true;
    case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT:
    default:
      // We need to get the application info to get the component's default state
      try {
        PackageInfo packageInfo = pm.getPackageInfo(pkgName, PackageManager.GET_ACTIVITIES
            | PackageManager.GET_RECEIVERS
            | PackageManager.GET_SERVICES
            | PackageManager.GET_PROVIDERS
            | PackageManager.GET_DISABLED_COMPONENTS);

        List<ComponentInfo> components = new ArrayList<>();
        if (packageInfo.activities != null) Collections.addAll(components, packageInfo.activities);
        if (packageInfo.services != null) Collections.addAll(components, packageInfo.services);
        if (packageInfo.providers != null) Collections.addAll(components, packageInfo.providers);

        for (ComponentInfo componentInfo : components) {
          if (componentInfo.name.equals(clsName)) {
            return componentInfo.enabled; //This is the default value (set in AndroidManifest.xml)
            //return componentInfo.isEnabled(); //Whole package dependant
          }
        }

        // the component is not declared in the AndroidManifest
        return false;
      } catch (PackageManager.NameNotFoundException e) {
        // the package isn't installed on the device
        return false;
      }
  }
}
BamsBamx
  • 4,139
  • 4
  • 38
  • 63
  • @androiddeveloper I would suggest (I am actually using it) the Jared Rummler posted code, but with a small change: instead of ´componentInfo.isEnabled();´ use ´componentInfo.enabled;´. See the edit... – BamsBamx Jan 31 '17 at 12:21
  • Wait, so isEnabled is wrongly used in his answer? It shouldn't be used in this case? – android developer Jan 31 '17 at 12:51
  • @androiddeveloper it depends on what you consider a component (en/dis)abled. Use isEnabled() when you consider all app components disabled when the app itself is disabled – BamsBamx Feb 02 '17 at 21:22
  • Oh, so componentInfo.enabled is just for a single component within the app, yet componentInfo.isEnabled() is for the entire app?! How odd! – android developer Feb 04 '17 at 10:00
  • @androiddeveloper yes, but note if the app is enabled the result of isEnabled() is going to be wether that component is enabled or not – BamsBamx Feb 04 '17 at 20:15
  • Even weirder. So how can you check if the app is enabled? – android developer Feb 04 '17 at 21:27
  • 1
    @androiddeveloper by getting ApplicationInfo of the app using PackageManager and reading ApplicationInfo.enabled field – BamsBamx Feb 05 '17 at 00:45
0

The code of Jared Rummler is good and it work but it takes the status in the manifest.

To have the current status of a component inside my app I needed to create the "ComponentName" through the "Package Context" and the "Component Class" and not the "Package Name" and "Class Name".

I had a BroadcastReceiver setted to "enabled = false" in the manifest, after that I enabled it inside my app using the package manager, but the Jared Rummler's codes always return me "STATE_ENABLED_DEFAULT" and "enabled and isEnabled()" always returned false. Using the "Package Context" and the "Component Class" i get directly the "ENABLED_STATE_DISABLED" and "ENABLED_STATE_ENABLED" without using the code in the default part, also the "enabled and isEnabled()" returned me anyway FALSE if I've started the receiver using the package manager.

Hope this is useful, see u

public static void enableDisableComponent(Context pckg, Class componentClass, boolean enable){
ComponentName component = new ComponentName(pckg, componentClass);
if(enable == !checkIfComponentIsEnabled(pckg, componentClass)) {
    pckg.getPackageManager().setComponentEnabledSetting(
        component,
        enable ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
        PackageManager.DONT_KILL_APP
    );
}

}

public static boolean checkIfComponentIsEnabled(Context pckg, Class componentClass){
            boolean ret = false;
            ComponentName componentName = new ComponentName(pckg, componentClass);
            int componentEnabled = pckg.getPackageManager().getComponentEnabledSetting(componentName);
            switch(componentEnabled){
                case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
                    break;
                case PackageManager.COMPONENT_ENABLED_STATE_ENABLED:
                    ret = true;
                    break;
                case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT:
                default:
                    ret = checkEnabledComponentsInfo(pckg, componentClass);
                    break;
            }
            return ret;
        }


private static boolean checkEnabledComponentsInfo(Context pckg, Class componentClass){
    boolean ret = false;
    try{
        PackageInfo packageInfo = pckg.getPackageManager().getPackageInfo(
            pckg.getPackageName(),
            PackageManager.GET_ACTIVITIES | PackageManager.GET_RECEIVERS |
                PackageManager.GET_SERVICES | PackageManager.GET_PROVIDERS |
                PackageManager.GET_DISABLED_COMPONENTS
        );
        List<ComponentInfo> componentsInfo = new ArrayList<>();
        if(packageInfo.activities != null && packageInfo.activities.length > 0){
            Collections.addAll(componentsInfo, packageInfo.activities);
        }
        if(packageInfo.services != null && packageInfo.services.length > 0){
            Collections.addAll(componentsInfo, packageInfo.services);
        }
        if(packageInfo.providers != null && packageInfo.providers.length > 0){
            Collections.addAll(componentsInfo, packageInfo.providers);
        }
        if(packageInfo.receivers != null && packageInfo.receivers.length > 0){
            Collections.addAll(componentsInfo, packageInfo.receivers);
        }
        if(componentsInfo.size() > 0){
            for(ComponentInfo info : componentsInfo){
                if(info.name.equals(componentClass.getName())){
                    ret = info.isEnabled();
                    break;
                }
            }
        }
    } catch(PackageManager.NameNotFoundException nnfE){
        onException(nnfE);
    }
    return ret;
}
Z3R0
  • 1,011
  • 10
  • 19
  • 1
    Added, "enabled" and "isEnabled" still me return "false" if I enabled my receiver through the PackageManager, but the first switch return me "STATE_ENABLED" or "STATE_DISABLED" if I enabled/disable it through the PackageManager. – Z3R0 Jan 17 '19 at 09:04
  • Thank you. Can you please also show an example of how to use it in case we do it of an app that isn't the current app? – android developer Jan 17 '19 at 09:16
  • I think that if u need to use it in a app that isn't the current app you need to use the "Package Name" and "Class Name" as Jared wrote. – Z3R0 Jan 17 '19 at 09:26
  • I don't understand. So a good thing to do is to use his functions for other apps, and yours in case it's the current app? – android developer Jan 17 '19 at 11:31
  • 1
    I think yes, I didn't try with the context case if in another app but I think it wouldn't work because of the restricted access to other's app context ( from the context of ur app u can't see the components of another app's package). In my case I was enabling a BluetoothAdapterReceiver with the PackageManager to catch the "BLUETOOTH_ENABLED" event when my app is closed to start a service to scan for BLE beacons. The check with the "Package Name" - "Class Name" wasn't working for me in my case (always go to DEFAULT of switch and enabled false), but with "Package Context" - "Class" it works. – Z3R0 Jan 17 '19 at 12:13
  • LOL sorry for really late question but I don't access & write so much on StackOverflow. Anyway I didn't tryed and this isn't "my functions" I just copyed "Jared Rummler" answer and changed the arguments and syntax of functions. Anyway I found that in some cases the "enableComponent" doesn't works properly, specially in last Android Releases (I don't know if I was doing anything wrong or something like that) so if you prefer you can init all receivers you need in the application and then register when you need them just by getting your Application instance and then register & unregister them – Z3R0 Oct 08 '19 at 07:39
  • As I said I got some problem when I tryed to start a custom receiver in my application class using the "enable method", I didn't tested that but I found that this method always worked when I was registering a receiver which will receive a "system action" intent but didn't work when I was register a custom receiver with a custom action intent. I don't know if this is kind of security rule about this way of enabling receivers (it can knowing the security enforcement is Google doing) (I won't test it sorry) xD bye, have a nice coding – Z3R0 Oct 08 '19 at 07:53