diff --git a/app/build.gradle b/app/build.gradle
index 71d3e8b78..db496e311 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -440,7 +440,6 @@ dependencies {
implementation libs.androidx.recyclerview
implementation libs.material.material
implementation libs.androidx.legacy.support
- implementation libs.androidx.cardview
implementation libs.androidx.preference
implementation libs.androidx.legacy.preference
implementation libs.androidx.gridlayout
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ClippedCardView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ClippedCardView.kt
index 08effd2e8..d3030626b 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/ClippedCardView.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/ClippedCardView.kt
@@ -6,8 +6,8 @@ import android.graphics.Path
import android.graphics.Rect
import android.graphics.RectF
import android.util.AttributeSet
-import androidx.cardview.widget.CardView
import androidx.core.graphics.withClip
+import com.google.android.material.card.MaterialCardView
/**
* Adds manual clipping around the card. This ensures that software rendering
@@ -16,7 +16,7 @@ import androidx.core.graphics.withClip
class ClippedCardView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
-) : CardView(context, attrs) {
+) : MaterialCardView(context, attrs) {
private val bounds = Rect()
private val boundsF = RectF()
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/RotatableGradientDrawable.java b/app/src/main/java/org/thoughtcrime/securesms/components/RotatableGradientDrawable.java
index b5160e4fe..b7cd65486 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/RotatableGradientDrawable.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/RotatableGradientDrawable.java
@@ -28,7 +28,7 @@ import kotlin.jvm.functions.Function2;
* fill the bounds with a gradient.
*
* If you wish to apply clipping to this drawable, it is recommended to either use it with
- * a CardView or utilize {@link org.thoughtcrime.securesms.util.CustomDrawWrapperKt#customizeOnDraw(Drawable, Function2)}
+ * a MaterialCardView or utilize {@link org.thoughtcrime.securesms.util.CustomDrawWrapperKt#customizeOnDraw(Drawable, Function2)}
*/
public final class RotatableGradientDrawable extends Drawable {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantsLayout.java b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantsLayout.java
index 0c3928c74..a6d2835e1 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantsLayout.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantsLayout.java
@@ -7,10 +7,10 @@ import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.cardview.widget.CardView;
import com.google.android.flexbox.AlignItems;
import com.google.android.flexbox.FlexboxLayout;
+import com.google.android.material.card.MaterialCardView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.events.CallParticipant;
@@ -116,7 +116,7 @@ public class CallParticipantsLayout extends FlexboxLayout {
private void update(int index, int count, @NonNull CallParticipant participant) {
View view = getChildAt(index);
- CardView cardView = view.findViewById(R.id.group_call_participant_card_wrapper);
+ MaterialCardView cardView = view.findViewById(R.id.group_call_participant_card_wrapper);
CallParticipantView callParticipantView = view.findViewById(R.id.group_call_participant);
callParticipantView.setCallParticipant(participant);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/Camera1Fragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/Camera1Fragment.java
index 7040f9311..4f075e6cb 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/Camera1Fragment.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/Camera1Fragment.java
@@ -29,7 +29,6 @@ import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.cardview.widget.CardView;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
@@ -39,6 +38,7 @@ import com.bumptech.glide.load.Transformation;
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
import com.bumptech.glide.request.target.SimpleTarget;
import com.bumptech.glide.request.transition.Transition;
+import com.google.android.material.card.MaterialCardView;
import org.signal.core.util.Stopwatch;
import org.signal.core.util.logging.Log;
@@ -360,9 +360,9 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment,
}
private void initializeViewFinderAndControlsPositioning() {
- CardView cameraCard = requireView().findViewById(R.id.camera_preview_parent);
- View controls = requireView().findViewById(R.id.camera_controls_container);
- CameraDisplay cameraDisplay = CameraDisplay.getDisplay(requireActivity());
+ MaterialCardView cameraCard = requireView().findViewById(R.id.camera_preview_parent);
+ View controls = requireView().findViewById(R.id.camera_controls_container);
+ CameraDisplay cameraDisplay = CameraDisplay.getDisplay(requireActivity());
if (!cameraDisplay.getRoundViewFinderCorners()) {
cameraCard.setRadius(0f);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.java
index a97d69567..f5bc3c96f 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.java
@@ -26,7 +26,6 @@ import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCaptureException;
@@ -35,13 +34,13 @@ import androidx.camera.view.CameraController;
import androidx.camera.view.LifecycleCameraController;
import androidx.camera.view.PreviewView;
import androidx.camera.view.video.ExperimentalVideo;
-import androidx.cardview.widget.CardView;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
import androidx.core.content.ContextCompat;
import com.bumptech.glide.Glide;
import com.bumptech.glide.util.Executors;
+import com.google.android.material.card.MaterialCardView;
import org.signal.core.util.Stopwatch;
import org.signal.core.util.concurrent.SimpleTask;
@@ -293,9 +292,9 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
}
private void initializeViewFinderAndControlsPositioning() {
- CardView cameraCard = requireView().findViewById(R.id.camerax_camera_parent);
- View controls = requireView().findViewById(R.id.camerax_controls_container);
- CameraDisplay cameraDisplay = CameraDisplay.getDisplay(requireActivity());
+ MaterialCardView cameraCard = requireView().findViewById(R.id.camerax_camera_parent);
+ View controls = requireView().findViewById(R.id.camerax_controls_container);
+ CameraDisplay cameraDisplay = CameraDisplay.getDisplay(requireActivity());
if (!cameraDisplay.getRoundViewFinderCorners()) {
cameraCard.setRadius(0f);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt
index 0ceeca9e2..581494412 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt
@@ -18,7 +18,6 @@ import android.view.View
import android.view.animation.Interpolator
import android.widget.FrameLayout
import android.widget.TextView
-import androidx.cardview.widget.CardView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.content.ContextCompat
@@ -30,6 +29,7 @@ import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import com.google.android.material.button.MaterialButton
+import com.google.android.material.card.MaterialCardView
import com.google.android.material.progressindicator.CircularProgressIndicatorSpec
import com.google.android.material.progressindicator.IndeterminateDrawable
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
@@ -176,7 +176,7 @@ class StoryViewerPageFragment :
val moreButton: View = view.findViewById(R.id.more)
val distributionList: TextView = view.findViewById(R.id.distribution_list)
val cardWrapper: TouchInterceptingFrameLayout = view.findViewById(R.id.story_content_card_touch_interceptor)
- val card: CardView = view.findViewById(R.id.story_content_card)
+ val card: MaterialCardView = view.findViewById(R.id.story_content_card)
val caption: TextView = view.findViewById(R.id.story_caption)
val largeCaption: TextView = view.findViewById(R.id.story_large_caption)
val largeCaptionOverlay: View = view.findViewById(R.id.story_large_caption_overlay)
@@ -610,7 +610,7 @@ class StoryViewerPageFragment :
private fun adjustConstraintsForScreenDimensions(
viewsAndReplies: View,
cardWrapper: View,
- card: CardView
+ card: MaterialCardView
) {
val constraintSet = ConstraintSet()
constraintSet.clone(storyPageContainer)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/StoriesSharedElementCrossFaderView.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/StoriesSharedElementCrossFaderView.kt
index 701df9a79..74a9b52f6 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/StoriesSharedElementCrossFaderView.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/StoriesSharedElementCrossFaderView.kt
@@ -6,11 +6,11 @@ import android.graphics.drawable.Drawable
import android.net.Uri
import android.util.AttributeSet
import android.widget.ImageView
-import androidx.cardview.widget.CardView
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
+import com.google.android.material.card.MaterialCardView
import org.signal.core.util.DimensionUnit
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.animation.transitions.CrossfaderTransition
@@ -24,7 +24,7 @@ import kotlin.reflect.KProperty
class StoriesSharedElementCrossFaderView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
-) : CardView(context, attrs), CrossfaderTransition.Crossfadeable {
+) : MaterialCardView(context, attrs), CrossfaderTransition.Crossfadeable {
companion object {
val CORNER_RADIUS_START = DimensionUnit.DP.toPixels(12f)
diff --git a/app/src/main/res/layout/camera_fragment.xml b/app/src/main/res/layout/camera_fragment.xml
index fb1603914..ec0787af7 100644
--- a/app/src/main/res/layout/camera_fragment.xml
+++ b/app/src/main/res/layout/camera_fragment.xml
@@ -6,7 +6,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
-
-
+
-
-
+
-
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/custom_chat_color_creator_fragment_page.xml b/app/src/main/res/layout/custom_chat_color_creator_fragment_page.xml
index 7c0bc3d74..0a95d3668 100644
--- a/app/src/main/res/layout/custom_chat_color_creator_fragment_page.xml
+++ b/app/src/main/res/layout/custom_chat_color_creator_fragment_page.xml
@@ -12,7 +12,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
-
-
+
-
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/group_call_participant_item.xml b/app/src/main/res/layout/group_call_participant_item.xml
index 50f69075d..b836458c1 100644
--- a/app/src/main/res/layout/group_call_participant_item.xml
+++ b/app/src/main/res/layout/group_call_participant_item.xml
@@ -9,7 +9,7 @@
tools:layout_height="match_parent"
tools:layout_width="match_parent">
-
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/group_pending_member_invites_fragment.xml b/app/src/main/res/layout/group_pending_member_invites_fragment.xml
index 322018957..e87843f8a 100644
--- a/app/src/main/res/layout/group_pending_member_invites_fragment.xml
+++ b/app/src/main/res/layout/group_pending_member_invites_fragment.xml
@@ -15,7 +15,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
-
-
+
-
-
+
diff --git a/app/src/main/res/layout/group_requesting_member_fragment.xml b/app/src/main/res/layout/group_requesting_member_fragment.xml
index fd86744cf..c15704762 100644
--- a/app/src/main/res/layout/group_requesting_member_fragment.xml
+++ b/app/src/main/res/layout/group_requesting_member_fragment.xml
@@ -14,7 +14,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
-
-
+
diff --git a/app/src/main/res/layout/message_details_recipient.xml b/app/src/main/res/layout/message_details_recipient.xml
index d44fcfbda..a4e855ac0 100644
--- a/app/src/main/res/layout/message_details_recipient.xml
+++ b/app/src/main/res/layout/message_details_recipient.xml
@@ -1,14 +1,14 @@
-
+ app:cardElevation="0dp"
+ tools:viewBindingIgnore="true">
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/message_details_recipient_header.xml b/app/src/main/res/layout/message_details_recipient_header.xml
index 9c0234af0..3f5d2c2d2 100644
--- a/app/src/main/res/layout/message_details_recipient_header.xml
+++ b/app/src/main/res/layout/message_details_recipient_header.xml
@@ -1,14 +1,14 @@
-
+ app:cardElevation="0dp"
+ tools:viewBindingIgnore="true">
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/popup_megaphone_view.xml b/app/src/main/res/layout/popup_megaphone_view.xml
index 13c9a376d..bbeb014e7 100644
--- a/app/src/main/res/layout/popup_megaphone_view.xml
+++ b/app/src/main/res/layout/popup_megaphone_view.xml
@@ -6,7 +6,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
-
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/processing_payment_dialog.xml b/app/src/main/res/layout/processing_payment_dialog.xml
index b358c306c..caacf2131 100644
--- a/app/src/main/res/layout/processing_payment_dialog.xml
+++ b/app/src/main/res/layout/processing_payment_dialog.xml
@@ -1,5 +1,5 @@
-
-
+
diff --git a/app/src/main/res/layout/redeeming_gift_dialog.xml b/app/src/main/res/layout/redeeming_gift_dialog.xml
index 808e5f431..6f81bc897 100644
--- a/app/src/main/res/layout/redeeming_gift_dialog.xml
+++ b/app/src/main/res/layout/redeeming_gift_dialog.xml
@@ -1,5 +1,5 @@
-
-
+
diff --git a/app/src/main/res/layout/stories_link_popup.xml b/app/src/main/res/layout/stories_link_popup.xml
index 5ab58ed1a..6038696f9 100644
--- a/app/src/main/res/layout/stories_link_popup.xml
+++ b/app/src/main/res/layout/stories_link_popup.xml
@@ -21,7 +21,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/bubble" />
-
+ tools:parentTag="com.google.android.material.card.MaterialCardView">
-
-
+
-
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/stories_viewer_fragment_page.xml b/app/src/main/res/layout/stories_viewer_fragment_page.xml
index 2611a9cd1..f8bf5b370 100644
--- a/app/src/main/res/layout/stories_viewer_fragment_page.xml
+++ b/app/src/main/res/layout/stories_viewer_fragment_page.xml
@@ -23,7 +23,7 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0">
-
-
+
-
-
+
diff --git a/app/src/main/res/layout/webrtc_call_participant_recycler_item.xml b/app/src/main/res/layout/webrtc_call_participant_recycler_item.xml
index ac9c34fec..dfda46744 100644
--- a/app/src/main/res/layout/webrtc_call_participant_recycler_item.xml
+++ b/app/src/main/res/layout/webrtc_call_participant_recycler_item.xml
@@ -1,5 +1,5 @@
-
-
+
diff --git a/app/src/main/res/layout/webrtc_call_view.xml b/app/src/main/res/layout/webrtc_call_view.xml
index 565a47fe2..37f636e27 100644
--- a/app/src/main/res/layout/webrtc_call_view.xml
+++ b/app/src/main/res/layout/webrtc_call_view.xml
@@ -176,7 +176,7 @@
app:layout_constraintTop_toTopOf="parent"
tools:visibility="gone">
-
-
+
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/contacts/app/src/main/res/layout/parent_item.xml b/contacts/app/src/main/res/layout/parent_item.xml
index e6552ceed..f735f901d 100644
--- a/contacts/app/src/main/res/layout/parent_item.xml
+++ b/contacts/app/src/main/res/layout/parent_item.xml
@@ -9,7 +9,7 @@
android:clipToPadding="false"
android:clipChildren="false">
-
-
+
\ No newline at end of file
diff --git a/dependencies.gradle b/dependencies.gradle
index 0550a5438..497c6b107 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -50,7 +50,6 @@ dependencyResolutionManagement {
alias('androidx-recyclerview').to('androidx.recyclerview:recyclerview:1.2.1')
alias('androidx-legacy-support').to('androidx.legacy:legacy-support-v13:1.0.0')
alias('androidx-legacy-preference').to('androidx.legacy:legacy-preference-v14:1.0.0')
- alias('androidx-cardview').to('androidx.cardview:cardview:1.0.0')
alias('androidx-preference').to('androidx.preference:preference:1.0.0')
alias('androidx-gridlayout').to('androidx.gridlayout:gridlayout:1.0.0')
alias('androidx-exifinterface').to('androidx.exifinterface:exifinterface:1.3.3')
diff --git a/lintchecks/src/main/java/org/signal/lint/CardViewDetector.java b/lintchecks/src/main/java/org/signal/lint/CardViewDetector.java
new file mode 100644
index 000000000..84ad79754
--- /dev/null
+++ b/lintchecks/src/main/java/org/signal/lint/CardViewDetector.java
@@ -0,0 +1,82 @@
+package org.signal.lint;
+
+import com.android.tools.lint.client.api.JavaEvaluator;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.LintFix;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.intellij.psi.PsiMethod;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.uast.UCallExpression;
+import org.jetbrains.uast.UExpression;
+
+import java.util.Collections;
+import java.util.List;
+
+@SuppressWarnings("UnstableApiUsage")
+public final class CardViewDetector extends Detector implements Detector.UastScanner {
+
+ static final Issue CARD_VIEW_USAGE = Issue.create("CardViewUsage",
+ "Utilizing CardView instead of MaterialCardView subclass",
+ "Signal utilizes MaterialCardView for more consistent and pleasant CardViews.",
+ Category.MESSAGES,
+ 5,
+ Severity.WARNING,
+ new Implementation(CardViewDetector.class, Scope.JAVA_FILE_SCOPE));
+
+ @Override
+ public @Nullable List getApplicableConstructorTypes() {
+ return Collections.singletonList("androidx.cardview.widget.CardView");
+ }
+
+ @Override
+ public void visitConstructor(JavaContext context, @NotNull UCallExpression call, @NotNull PsiMethod method) {
+ JavaEvaluator evaluator = context.getEvaluator();
+
+ if (evaluator.isMemberInClass(method, "androidx.cardview.widget.CardView")) {
+ LintFix fix = quickFixIssueAlertDialogBuilder(call);
+ context.report(CARD_VIEW_USAGE,
+ call,
+ context.getLocation(call),
+ "Using 'androidx.cardview.widget.CardView' instead of com.google.android.material.card.MaterialCardView",
+ fix);
+ }
+ }
+
+ private LintFix quickFixIssueAlertDialogBuilder(@NotNull UCallExpression alertBuilderCall) {
+ List arguments = alertBuilderCall.getValueArguments();
+ UExpression context = arguments.get(0);
+
+ String fixSource = "new com.google.android.material.card.MaterialCardView";
+
+ //Context context, AttributeSet attrs, int defStyleAttr
+ switch (arguments.size()) {
+ case 1:
+ fixSource += String.format("(%s)", context);
+ break;
+ case 2:
+ UExpression attrs = arguments.get(1);
+ fixSource += String.format("(%s, %s)", context, attrs);
+ break;
+ case 3:
+ UExpression attributes = arguments.get(1);
+ UExpression defStyleAttr = arguments.get(2);
+ fixSource += String.format("(%s, %s, %s)", context, attributes, defStyleAttr);
+ break;
+
+ default:
+ throw new IllegalStateException("MaterialAlertDialogBuilder overloads should have 1 or 2 arguments");
+ }
+
+ String builderCallSource = alertBuilderCall.asSourceString();
+ LintFix.GroupBuilder fixGrouper = fix().group();
+ fixGrouper.add(fix().replace().text(builderCallSource).shortenNames().reformat(true).with(fixSource).build());
+ return fixGrouper.build();
+ }
+}
\ No newline at end of file
diff --git a/lintchecks/src/main/java/org/signal/lint/Registry.java b/lintchecks/src/main/java/org/signal/lint/Registry.java
index 6152e3a5a..9fe531473 100644
--- a/lintchecks/src/main/java/org/signal/lint/Registry.java
+++ b/lintchecks/src/main/java/org/signal/lint/Registry.java
@@ -28,7 +28,8 @@ public final class Registry extends IssueRegistry {
BlockingGetDetector.UNSAFE_BLOCKING_GET,
RecipientIdDatabaseDetector.RECIPIENT_ID_DATABASE_REFERENCE_ISSUE,
ThreadIdDatabaseDetector.THREAD_ID_DATABASE_REFERENCE_ISSUE,
- StartForegroundServiceDetector.START_FOREGROUND_SERVICE_ISSUE);
+ StartForegroundServiceDetector.START_FOREGROUND_SERVICE_ISSUE,
+ CardViewDetector.CARD_VIEW_USAGE);
}
@Override
diff --git a/lintchecks/src/test/java/org/signal/lint/CardViewDetectorTest.java b/lintchecks/src/test/java/org/signal/lint/CardViewDetectorTest.java
new file mode 100644
index 000000000..a6dd6924d
--- /dev/null
+++ b/lintchecks/src/test/java/org/signal/lint/CardViewDetectorTest.java
@@ -0,0 +1,100 @@
+package org.signal.lint;
+
+import com.android.tools.lint.checks.infrastructure.TestFile;
+
+import org.junit.Test;
+
+import java.io.InputStream;
+import java.util.Scanner;
+
+import static com.android.tools.lint.checks.infrastructure.TestFiles.java;
+import static com.android.tools.lint.checks.infrastructure.TestLintTask.lint;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+@SuppressWarnings("UnstableApiUsage")
+public final class CardViewDetectorTest {
+
+ private static final TestFile cardViewStub = java(readResourceAsString("CardViewStub.java"));
+
+ @Test
+ public void cardViewUsed_LogCardViewUsage_1_arg() {
+ lint()
+ .files(cardViewStub,
+ java("package foo;\n" +
+ "import androidx.cardview.widget.CardView;\n" +
+ "public class Example {\n" +
+ " public void buildDialog() {\n" +
+ " new CardView(context);\n" +
+ " }\n" +
+ "}")
+ )
+ .issues(AlertDialogBuilderDetector.ALERT_DIALOG_BUILDER_USAGE)
+ .run()
+ .expect("src/foo/Example.java:5: Warning: Using 'androidx.cardview.widget.CardView' instead of com.google.android.material.card.MaterialCardView [CardViewUsage]\n" +
+ " new CardView(context);\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 1 warnings")
+ .expectFixDiffs("Fix for src/foo/Example.java line 5: Replace with new com.google.android.material.card.MaterialCardView(context):\n" +
+ "@@ -5 +5\n" +
+ "- new CardView(context);\n" +
+ "+ new com.google.android.material.card.MaterialCardView(context);");
+ }
+
+ @Test
+ public void cardViewUsed_LogCardViewUsage_2_arg() {
+ lint()
+ .files(cardViewStub,
+ java("package foo;\n" +
+ "import androidx.cardview.widget.CardView;\n" +
+ "public class Example {\n" +
+ " public void buildDialog() {\n" +
+ " new CardView(context, attrs);\n" +
+ " }\n" +
+ "}")
+ )
+ .issues(AlertDialogBuilderDetector.ALERT_DIALOG_BUILDER_USAGE)
+ .run()
+ .expect("src/foo/Example.java:5: Warning: Using 'androidx.cardview.widget.CardView' instead of com.google.android.material.card.MaterialCardView [CardViewUsage]\n" +
+ " new CardView(context, attrs);\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 1 warnings")
+ .expectFixDiffs("Fix for src/foo/Example.java line 5: Replace with new com.google.android.material.card.MaterialCardView(context, attrs):\n" +
+ "@@ -5 +5\n" +
+ "- new CardView(context, attrs);\n" +
+ "+ new com.google.android.material.card.MaterialCardView(context, attrs);");
+ }
+
+ @Test
+ public void cardViewUsed_withAssignment_LogCardViewUsage_1_arg() {
+ lint()
+ .files(cardViewStub,
+ java("package foo;\n" +
+ "import androidx.cardview.widget.CardView;\n" +
+ "public class Example {\n" +
+ " public void buildDialog() {\n" +
+ " CardView cardView = new CardView(context)\n" +
+ " ;\n" +
+ " }\n" +
+ "}")
+ )
+ .issues(AlertDialogBuilderDetector.ALERT_DIALOG_BUILDER_USAGE)
+ .run()
+ .expect("src/foo/Example.java:5: Warning: Using 'androidx.cardview.widget.CardView' instead of com.google.android.material.card.MaterialCardView [CardViewUsage]\n" +
+ " CardView cardView = new CardView(context)\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 1 warnings")
+ .expectFixDiffs("Fix for src/foo/Example.java line 5: Replace with new com.google.android.material.card.MaterialCardView(context):\n" +
+ "@@ -5 +5\n" +
+ "- CardView cardView = new CardView(context)\n" +
+ "+ CardView cardView = new com.google.android.material.card.MaterialCardView(context)");
+ }
+
+ private static String readResourceAsString(String resourceName) {
+ InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName);
+ assertNotNull(inputStream);
+ Scanner scanner = new Scanner(inputStream).useDelimiter("\\A");
+ assertTrue(scanner.hasNext());
+ return scanner.next();
+ }
+}
diff --git a/lintchecks/src/test/resources/CardViewStub.java b/lintchecks/src/test/resources/CardViewStub.java
new file mode 100644
index 000000000..97860064a
--- /dev/null
+++ b/lintchecks/src/test/resources/CardViewStub.java
@@ -0,0 +1,16 @@
+package androidx.appcompat.app;
+
+public class CardView {
+
+ public CardView(Context context) {
+
+ }
+
+ public CardView(Context context, AttributeSet attrs) {
+
+ }
+
+ public CardView(Context context, AttributeSet attrs, int defStyleAttr) {
+
+ }
+}
diff --git a/paging/app/src/main/res/layout/item.xml b/paging/app/src/main/res/layout/item.xml
index a0cd66dbd..3c2e797f4 100644
--- a/paging/app/src/main/res/layout/item.xml
+++ b/paging/app/src/main/res/layout/item.xml
@@ -9,7 +9,7 @@
android:clipToPadding="false"
android:clipChildren="false">
-
-
+
\ No newline at end of file
diff --git a/spinner/app/src/main/res/layout/item.xml b/spinner/app/src/main/res/layout/item.xml
index a0cd66dbd..3c2e797f4 100644
--- a/spinner/app/src/main/res/layout/item.xml
+++ b/spinner/app/src/main/res/layout/item.xml
@@ -9,7 +9,7 @@
android:clipToPadding="false"
android:clipChildren="false">
-
-
+
\ No newline at end of file