package android.content.pm;

import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
import static android.content.pm.PackageManager.DONT_KILL_APP;
import static android.content.pm.PackageManager.GET_ACTIVITIES;
import static android.content.pm.PackageManager.GET_RESOLVED_FILTER;
import static android.content.pm.PackageManager.GET_SERVICES;
import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
import static android.os.Build.VERSION_CODES.O;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
import java.util.ArrayList;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import org.robolectric.testapp.TestService;

/** Compatibility test for {@link PackageManager} */
@RunWith(AndroidJUnit4.class)
public final class PackageManagerTest {
  private Context context;
  private PackageManager pm;

  @Before
  public void setup() throws Exception {
    context = ApplicationProvider.getApplicationContext();
    pm = context.getPackageManager();
  }

  @After
  public void tearDown() {
    pm.setApplicationEnabledSetting(
        context.getPackageName(), COMPONENT_ENABLED_STATE_DEFAULT, DONT_KILL_APP);
    pm.setComponentEnabledSetting(
        new ComponentName(context, "org.robolectric.testapp.TestActivity"),
        COMPONENT_ENABLED_STATE_DEFAULT,
        DONT_KILL_APP);
    pm.setComponentEnabledSetting(
        new ComponentName(context, "org.robolectric.testapp.DisabledTestActivity"),
        COMPONENT_ENABLED_STATE_DEFAULT,
        DONT_KILL_APP);
  }

  @Test
  @Config(minSdk = O)
  @SdkSuppress(minSdkVersion = O)
  public void isInstantApp_shouldNotBlowup() {
    assertThat(context.getPackageManager().isInstantApp()).isFalse();
  }

  @Test
  public void getPackageInfo() throws Exception {
    PackageInfo info =
        pm.getPackageInfo(
            context.getPackageName(), MATCH_DISABLED_COMPONENTS | GET_ACTIVITIES | GET_SERVICES);
    ActivityInfo[] activities = filterExtraneous(info.activities);

    assertThat(activities).hasLength(4);
    assertThat(info.services).hasLength(1);

    assertThat(activities[0].name).isEqualTo("org.robolectric.testapp.TestActivity");
    assertThat(activities[0].enabled).isTrue();
    assertThat(activities[1].name).isEqualTo("org.robolectric.testapp.DisabledTestActivity");
    assertThat(activities[1].enabled).isFalse();

    assertThat(info.services[0].name).isEqualTo("org.robolectric.testapp.TestService");
    assertThat(info.services[0].enabled).isTrue();
  }

  @Test
  public void getPackageInfo_noFlagsGetNoComponents() throws Exception {
    PackageInfo info = pm.getPackageInfo(context.getPackageName(), 0);
    assertThat(info.activities).isNull();
    assertThat(info.services).isNull();
  }

  @Test
  public void getPackageInfo_skipsDisabledComponents() throws Exception {
    PackageInfo info = pm.getPackageInfo(context.getPackageName(), GET_ACTIVITIES);
    ActivityInfo[] activities = filterExtraneous(info.activities);

    assertThat(activities).hasLength(3);
    assertThat(activities[0].name).isEqualTo("org.robolectric.testapp.TestActivity");
  }

  @Test
  public void getComponent_partialName() {
    ComponentName serviceName = new ComponentName(context, ".TestService");

    try {
      pm.getServiceInfo(serviceName, 0);
      fail("Expected NameNotFoundException");
    } catch (NameNotFoundException expected) {
    }
  }

  @Test
  public void getComponent_wrongNameActivity() {
    ComponentName activityName = new ComponentName(context, "WrongNameActivity");

    try {
      pm.getActivityInfo(activityName, 0);
      fail("Expected NameNotFoundException");
    } catch (NameNotFoundException expected) {
    }
  }

  @Test
  public void getComponent_validName() throws Exception {
    ComponentName componentName = new ComponentName(context, "org.robolectric.testapp.TestService");
    ServiceInfo info = pm.getServiceInfo(componentName, 0);

    assertThat(info).isNotNull();
  }

  @Test
  public void getComponent_validName_queryWithMoreFlags() throws Exception {
    ComponentName componentName = new ComponentName(context, "org.robolectric.testapp.TestService");
    ServiceInfo info = pm.getServiceInfo(componentName, MATCH_DISABLED_COMPONENTS);

    assertThat(info).isNotNull();
  }

  @Test
  public void queryIntentServices_noFlags() {
    List<ResolveInfo> result = pm.queryIntentServices(new Intent(context, TestService.class), 0);

    assertThat(result).hasSize(1);
  }

  @Test
  public void getComponent_disabledComponent_doesntInclude() {
    ComponentName disabledActivityName =
        new ComponentName(context, "org.robolectric.testapp.DisabledTestActivity");

    try {
      pm.getActivityInfo(disabledActivityName, 0);
      fail("NameNotFoundException expected");
    } catch (NameNotFoundException expected) {
    }
  }

  @Test
  public void getComponent_disabledComponent_include() throws Exception {
    ComponentName disabledActivityName =
        new ComponentName(context, "org.robolectric.testapp.DisabledTestActivity");

    ActivityInfo info = pm.getActivityInfo(disabledActivityName, MATCH_DISABLED_COMPONENTS);
    assertThat(info).isNotNull();
    assertThat(info.enabled).isFalse();
  }

  @Test
  public void getPackageInfo_programmaticallyDisabledComponent_noFlags_notReturned() {
    ComponentName activityName = new ComponentName(context, "org.robolectric.testapp.TestActivity");
    pm.setComponentEnabledSetting(activityName, COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP);

    try {
      pm.getActivityInfo(activityName, 0);
      fail("NameNotFoundException expected");
    } catch (NameNotFoundException expected) {
    }
  }

  @Test
  public void getPackageInfo_programmaticallyDisabledComponent_withFlags_returned()
      throws Exception {
    ComponentName activityName = new ComponentName(context, "org.robolectric.testapp.TestActivity");
    pm.setComponentEnabledSetting(activityName, COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP);

    ActivityInfo info = pm.getActivityInfo(activityName, MATCH_DISABLED_COMPONENTS);
    assertThat(info).isNotNull();
    // WHAT?? Seems like we always get the manifest value for ComponentInfo.enabled
    assertThat(info.enabled).isTrue();
    assertThat(info.isEnabled()).isTrue();
  }

  @Test
  public void getPackageInfo_programmaticallyEnabledComponent_returned() throws Exception {
    ComponentName activityName =
        new ComponentName(context, "org.robolectric.testapp.DisabledTestActivity");
    pm.setComponentEnabledSetting(activityName, COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);

    ActivityInfo info = pm.getActivityInfo(activityName, 0);
    assertThat(info).isNotNull();
    // WHAT?? Seems like we always get the manifest value for ComponentInfo.enabled
    assertThat(info.enabled).isFalse();
    assertThat(info.isEnabled()).isFalse();
  }

  @Test
  @Config(maxSdk = 23)
  @SdkSuppress(maxSdkVersion = 23)
  public void getPackageInfo_disabledApplication_stillReturned_below24() throws Exception {
    pm.setApplicationEnabledSetting(
        context.getPackageName(), COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP);

    PackageInfo packageInfo =
        pm.getPackageInfo(context.getPackageName(), GET_SERVICES | GET_ACTIVITIES);
    ActivityInfo[] activities = filterExtraneous(packageInfo.activities);

    assertThat(packageInfo.packageName).isEqualTo(context.getPackageName());
    assertThat(packageInfo.applicationInfo.enabled).isFalse();

    // Seems that although disabled app makes everything disabled it is still returned with its
    // manifest state below API 23
    assertThat(activities).hasLength(3);
    assertThat(packageInfo.services).hasLength(1);

    assertThat(activities[0].enabled).isTrue();
    assertThat(packageInfo.services[0].enabled).isTrue();
    assertThat(activities[0].isEnabled()).isFalse();
    assertThat(packageInfo.services[0].isEnabled()).isFalse();
  }

  @Test
  @Config(minSdk = 24)
  @SdkSuppress(minSdkVersion = 24)
  public void getPackageInfo_disabledApplication_stillReturned_after24() throws Exception {
    pm.setApplicationEnabledSetting(
        context.getPackageName(), COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP);

    PackageInfo packageInfo =
        pm.getPackageInfo(context.getPackageName(), GET_SERVICES | GET_ACTIVITIES);

    assertThat(packageInfo.packageName).isEqualTo(context.getPackageName());
    assertThat(packageInfo.applicationInfo.enabled).isFalse();

    // seems that since API 24 it is isEnabled() and not enabled that gets something into default
    // result
    assertThat(packageInfo.activities).isNull();
    assertThat(packageInfo.services).isNull();
  }

  @Test
  public void getPackageInfo_disabledApplication_withFlags_returnedEverything() throws Exception {
    pm.setApplicationEnabledSetting(
        context.getPackageName(), COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP);

    PackageInfo packageInfo =
        pm.getPackageInfo(
            context.getPackageName(), GET_SERVICES | GET_ACTIVITIES | MATCH_DISABLED_COMPONENTS);
    ActivityInfo[] activities = filterExtraneous(packageInfo.activities);

    assertThat(packageInfo.applicationInfo.enabled).isFalse();
    assertThat(packageInfo.packageName).isEqualTo(context.getPackageName());
    assertThat(activities).hasLength(4);
    assertThat(packageInfo.services).hasLength(1);
    assertThat(activities[0].enabled).isTrue(); // default enabled flag
  }

  @Test
  public void getApplicationInfo_disabledApplication_stillReturnedWithNoFlags() throws Exception {
    pm.setApplicationEnabledSetting(
        context.getPackageName(), COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP);

    ApplicationInfo applicationInfo = pm.getApplicationInfo(context.getPackageName(), 0);

    assertThat(applicationInfo.enabled).isFalse();
    assertThat(applicationInfo.packageName).isEqualTo(context.getPackageName());
  }

  @Test
  public void queryIntentActivities_packageOnly() {
    List<ResolveInfo> resolveInfos =
        pm.queryIntentActivities(
            new Intent().setPackage(context.getPackageName()),
            MATCH_DISABLED_COMPONENTS | GET_RESOLVED_FILTER);

    for (ResolveInfo resolveInfo : resolveInfos) {
      assertThat(resolveInfo.filter).isNotNull();
    }
  }

  private ActivityInfo[] filterExtraneous(ActivityInfo[] activities) {
    List<ActivityInfo> filtered = new ArrayList<>();
    for (ActivityInfo activity : activities) {
      if (activity.name.startsWith("org.robolectric")) {
        filtered.add(activity);
      }
    }
    return filtered.toArray(new ActivityInfo[0]);
  }
}
