content: /*
   * Copyright (C) 2015 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package androidx.test.espresso.intent.matcher;
  
  import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
  import static androidx.test.espresso.intent.Checks.checkNotNull;
  import static androidx.test.espresso.intent.matcher.BundleMatchers.doesNotHaveKey;
  import static androidx.test.espresso.intent.matcher.BundleMatchers.hasEntry;
  import static androidx.test.espresso.intent.matcher.BundleMatchers.hasKey;
  import static androidx.test.espresso.intent.matcher.ComponentNameMatchers.hasClassName;
  import static androidx.test.espresso.intent.matcher.ComponentNameMatchers.hasMyPackageName;
  import static org.hamcrest.Matchers.equalTo;
  import static org.hamcrest.Matchers.is;
  
  import android.content.ComponentName;
  import android.content.Context;
  import android.content.Intent;
  import android.net.Uri;
  import android.os.Bundle;
  import androidx.annotation.NonNull;
  import androidx.test.espresso.intent.ResolvedIntent;
  import com.google.errorprone.annotations.CheckReturnValue;
  import java.util.Set;
  import org.hamcrest.Description;
  import org.hamcrest.Matcher;
  import org.hamcrest.Matchers;
  import org.hamcrest.TypeSafeMatcher;
  
  /** A collection of hamcrest matchers for matching {@link Intent} objects. */
  @CheckReturnValue
  public final class IntentMatchers {
  
    private IntentMatchers() {}
  
    public static Matcher<Intent> anyIntent() {
      return new TypeSafeMatcher<Intent>() {
        @Override
        public void describeTo(Description description) {
          description.appendText("any intent");
        }
  
        @Override
        public boolean matchesSafely(Intent intent) {
          return true;
        }
      };
    }
  
    public static Matcher<Intent> hasAction(String action) {
      return hasAction(is(action));
    }
  
    public static Matcher<Intent> hasAction(final Matcher<String> actionMatcher) {
      checkNotNull(actionMatcher);
  
      return new TypeSafeMatcher<Intent>() {
        @Override
        public void describeTo(Description description) {
          description.appendText("has action: ");
          description.appendDescriptionOf(actionMatcher);
        }
  
        @Override
        public boolean matchesSafely(Intent intent) {
          return actionMatcher.matches(intent.getAction());
        }
      };
    }
  
    public static Matcher<Intent> hasCategories(Set<String> categories) {
      return hasCategories(equalTo((Iterable<String>) categories));
    }
  
    public static Matcher<Intent> hasCategories(
        final Matcher<? extends Iterable<? extends String>> categoriesMatcher) {
      checkNotNull(categoriesMatcher);
      return new TypeSafeMatcher<Intent>() {
        @Override
        public void describeTo(Description description) {
          description.appendText("has categories: ");
          description.appendDescriptionOf(categoriesMatcher);
        }
  
        @Override
        public boolean matchesSafely(Intent intent) {
          return categoriesMatcher.matches(intent.getCategories());
        }
      };
    }
  
    /**
     * Returns a matcher that will only match intents targeted to a single class by using {@link
     * ComponentNameMatchers#hasClassName}. The input string must contain the package name + short
     * class name. For example hasComponent("com.google.provider.NotePad").
     *
     * @param className complete class path
     */
    public static Matcher<Intent> hasComponent(String className) {
      return hasComponent(hasClassName(className));
    }
  
    /**
     * Returns a matcher that will only match intents targeted to the componentName's class, {@see
     * ComponentName#getClassName}. For example: Intent intent = new Intent() .setComponentName(new
     * ComponentName("com.google.provider", "com.google.provider.Notepad")); will match all intents
     * targeted to Notepad.java.
     *
     * @param componentName a componentName that has the target class specified
     */
    public static Matcher<Intent> hasComponent(ComponentName componentName) {
      return hasComponent(hasClassName(componentName.getClassName()));
    }
  
    /**
     * Can match an intent by class name, package name or short class name.
     *
     * @param componentMatcher can be the value of {@link ComponentNameMatchers#hasClassName}, {@link
     *     ComponentNameMatchers#hasPackageName} or {@link ComponentNameMatchers#hasShortClassName}
     */
    public static Matcher<Intent> hasComponent(final Matcher<ComponentName> componentMatcher) {
      checkNotNull(componentMatcher);
  
      return new TypeSafeMatcher<Intent>() {
        @Override
        public void describeTo(Description description) {
          description.appendText("has component: ");
          description.appendDescriptionOf(componentMatcher);
        }
  
        @Override
        public boolean matchesSafely(Intent intent) {
          return componentMatcher.matches(intent.getComponent());
        }
      };
    }
  
    public static Matcher<Intent> hasData(String uri) {
      return hasData(is(Uri.parse(uri)));
    }
  
    public static Matcher<Intent> hasData(Uri uri) {
      return hasData(is(uri));
    }
  
    public static Matcher<Intent> hasData(final Matcher<Uri> uriMatcher) {
      checkNotNull(uriMatcher);
  
      return new TypeSafeMatcher<Intent>() {
        @Override
        public void describeTo(Description description) {
          description.appendText("has data: ");
          description.appendDescriptionOf(uriMatcher);
        }
  
        @Override
        public boolean matchesSafely(Intent intent) {
          return uriMatcher.matches(intent.getData());
        }
      };
    }
  
    public static Matcher<Intent> hasDataString(final Matcher<String> stringMatcher) {
      checkNotNull(stringMatcher);
  
      return new TypeSafeMatcher<Intent>() {
        @Override
        public void describeTo(Description description) {
          description.appendText("has data string: ").appendDescriptionOf(stringMatcher);
        }
  
        @Override
        public boolean matchesSafely(Intent intent) {
          return stringMatcher.matches(intent.getDataString());
        }
      };
    }
  
    public static Matcher<Intent> hasExtraWithKey(String key) {
      return hasExtraWithKey(is(key));
    }
  
    public static Matcher<Intent> hasExtraWithKey(Matcher<String> keyMatcher) {
      return hasExtras(hasKey(keyMatcher));
    }
  
    @NonNull
    public static Matcher<Intent> doesNotHaveExtraWithKey(@NonNull String key) {
      return doesNotHaveExtraWithKey(is(key));
    }
  
    @NonNull
    public static Matcher<Intent> doesNotHaveExtraWithKey(@NonNull Matcher<String> keyMatcher) {
      return hasExtras(doesNotHaveKey(keyMatcher));
    }
  
    public static <T> Matcher<Intent> hasExtra(String key, T value) {
      return hasExtras(hasEntry(key, value));
    }
  
    public static Matcher<Intent> hasExtra(String key, Matcher<?> valueMatcher) {
      // The null check in here isn't ideal, but it's necessary for backwards compatibility.
      // Otherwise, the previously acceptable `hasExtra(key, null)` invocation will throw an NPE.
      return hasExtras(hasEntry(key, valueMatcher == null ? Matchers.nullValue() : valueMatcher));
    }
  
    public static Matcher<Intent> hasExtra(Matcher<String> keyMatcher, Matcher<?> valueMatcher) {
      return hasExtras(hasEntry(keyMatcher, valueMatcher));
    }
  
    public static Matcher<Intent> hasExtras(final Matcher<Bundle> bundleMatcher) {
      checkNotNull(bundleMatcher);
  
      return new TypeSafeMatcher<Intent>() {
        @Override
        public void describeTo(Description description) {
          description.appendText("has extras: ");
          description.appendDescriptionOf(bundleMatcher);
        }
  
        @Override
        public boolean matchesSafely(Intent intent) {
          return bundleMatcher.matches(intent.getExtras());
        }
      };
    }
  
    public static Matcher<Intent> hasType(String type) {
      return hasType(is(type));
    }
  
    public static Matcher<Intent> hasType(final Matcher<String> typeMatcher) {
      checkNotNull(typeMatcher);
  
      return new TypeSafeMatcher<Intent>() {
        @Override
        public void describeTo(Description description) {
          description.appendText("has type: ");
          description.appendDescriptionOf(typeMatcher);
        }
  
        @Override
        public boolean matchesSafely(Intent intent) {
          return typeMatcher.matches(intent.getType());
        }
      };
    }
  
    public static Matcher<Intent> hasPackage(final Matcher<String> packageMatcher) {
      checkNotNull(packageMatcher);
  
      return new TypeSafeMatcher<Intent>() {
        @Override
        public void describeTo(Description description) {
          description.appendText("has pkg: ");
          description.appendDescriptionOf(packageMatcher);
        }
  
        @Override
        public boolean matchesSafely(Intent intent) {
          return packageMatcher.matches(intent.getPackage());
        }
      };
    }
  
    public static Matcher<Intent> hasPackage(final String packageName) {
      checkNotNull(packageName);
      return hasPackage(equalTo(packageName));
    }
  
    /**
     * Matches an intent based on the package of activity which can handle the intent.
     *
     * @param packageName packages of activity that can handle the intent
     */
    public static Matcher<Intent> toPackage(final String packageName) {
      checkNotNull(packageName);
  
      return new TypeSafeMatcher<Intent>() {
        @Override
        public void describeTo(Description description) {
          description.appendText("resolvesTo: " + packageName);
        }
  
        @Override
        public boolean matchesSafely(Intent intent) {
          // Ideally, this would be a Matcher<ResolvedIntent> and we would not need this runtime
          // check. However, this matcher would then not work in combination with other matchers of
          // type Matcher<Intent> (as in the case with allOf(toPackage(...), hasType(...)).
          if (!(intent instanceof ResolvedIntent)) {
            throw new RuntimeException(
                String.format(
                    "toPackage.matches was given an intent that is not of type %s. This"
                        + " should not happen as this method is only invoked internally by Intents.",
                    ResolvedIntent.class.getSimpleName()));
          }
          return ((ResolvedIntent) intent).canBeHandledBy(packageName);
        }
      };
    }
  
    public static Matcher<Intent> hasFlag(int flag) {
      return hasFlags(flag);
    }
  
    public static Matcher<Intent> hasFlags(int... flags) {
      int allFlags = 0;
      for (int i : flags) {
        allFlags |= i;
      }
      return hasFlags(allFlags);
    }
  
    public static Matcher<Intent> hasFlags(final int flags) {
  
      return new TypeSafeMatcher<Intent>() {
        @Override
        public void describeTo(Description description) {
          description.appendText("flags: " + Integer.toHexString(flags));
        }
  
        @Override
        public boolean matchesSafely(Intent intent) {
          int intentFlags = intent.getFlags();
          return ((intentFlags & flags) == flags);
        }
      };
    }
  
    /** Matches an intent if it {@link Intent#filterEquals(Intent)} the expected intent. */
    public static Matcher<Intent> filterEquals(Intent expectedIntent) {
      return new TypeSafeMatcher<Intent>() {
        @Override
        public void describeTo(Description description) {
          description.appendText("filterEquals: ").appendValue(expectedIntent);
        }
  
        @Override
        public boolean matchesSafely(Intent intent) {
          return expectedIntent.filterEquals(intent);
        }
      };
    }
  
    /**
     * Matches an intent if its package is the same as the target package for the instrumentation
     * test.
     */
    public static Matcher<Intent> isInternal() {
      final Context targetContext = getApplicationContext();
  
      return new TypeSafeMatcher<Intent>() {
        @Override
        public void describeTo(Description description) {
          description.appendText("target package: " + targetContext.getPackageName());
        }
  
        @Override
        public boolean matchesSafely(Intent intent) {
          ComponentName component = intent.resolveActivity(targetContext.getPackageManager());
          if (component != null) {
            return hasMyPackageName().matches(component);
          }
          return false;
        }
      };
    }
  }
  