How to check if an Activity is enabled?
Asked Answered
C

3

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?

Caneghem answered 16/11, 2014 at 11:23 Comment(6)
What "any other app components" do you have in mind? Any of which are disabled by default?Cinereous
@user3249477 The API states you can perform this check on other app components : "activity, receiver, service, provider" . Check this link: developer.android.com/reference/android/content/pm/…Caneghem
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.Detachment
@CoryCharlton No. Maybe I should put a bounty?Caneghem
Did you try getActivityInfo() and the like, calling ComponentInfo.isEnabled() on them? It says Return whether this component and its enclosing application are enabled.Yettayetti
@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.Detachment
L
18

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

Laurentium answered 30/3, 2016 at 22:2 Comment(9)
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 android.googlesource.com/platform/frameworks/base/+/ee0b3e9/… 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.Detachment
@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.Laurentium
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.Detachment
@CoryCharlton You mean that it should also check if the app itself is disabled?Caneghem
@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.Detachment
@CoryCharlton I don't understand how can it be, and how it can be fixed. Please explain and give examples of what you mean.Caneghem
@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.Laurentium
@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. :)Caneghem
@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.Detachment
F
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;
      }
  }
}
Frameup answered 30/1, 2017 at 21:36 Comment(7)
@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...Frameup
Wait, so isEnabled is wrongly used in his answer? It shouldn't be used in this case?Caneghem
@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 disabledFrameup
Oh, so componentInfo.enabled is just for a single component within the app, yet componentInfo.isEnabled() is for the entire app?! How odd!Caneghem
@androiddeveloper yes, but note if the app is enabled the result of isEnabled() is going to be wether that component is enabled or notFrameup
Even weirder. So how can you check if the app is enabled?Caneghem
@androiddeveloper by getting ApplicationInfo of the app using PackageManager and reading ApplicationInfo.enabled fieldFrameup
F
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;
}
Fabianfabianism answered 17/1, 2019 at 8:53 Comment(7)
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.Fabianfabianism
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?Caneghem
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.Fabianfabianism
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?Caneghem
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.Fabianfabianism
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 themFabianfabianism
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 codingFabianfabianism

© 2022 - 2024 — McMap. All rights reserved.