Refine access analysis in bridge removal

Fixes: b/245882297
Change-Id: Id91b041cd7d28256ced5de829f56e54d215d7367
diff --git a/src/main/java/com/android/tools/r8/graph/Definition.java b/src/main/java/com/android/tools/r8/graph/Definition.java
index 05ad2d1..e03b8e8 100644
--- a/src/main/java/com/android/tools/r8/graph/Definition.java
+++ b/src/main/java/com/android/tools/r8/graph/Definition.java
@@ -155,4 +155,12 @@
   default ProgramMethod asProgramMethod() {
     return null;
   }
+
+  default boolean isSamePackage(Definition definition) {
+    return isSamePackage(definition.getReference());
+  }
+
+  default boolean isSamePackage(DexReference reference) {
+    return getReference().isSamePackage(reference);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexReference.java b/src/main/java/com/android/tools/r8/graph/DexReference.java
index 6956170..1e84335 100644
--- a/src/main/java/com/android/tools/r8/graph/DexReference.java
+++ b/src/main/java/com/android/tools/r8/graph/DexReference.java
@@ -83,6 +83,10 @@
     return null;
   }
 
+  public boolean isSamePackage(DexReference reference) {
+    return getContextType().isSamePackage(reference.getContextType());
+  }
+
   public int referenceTypeOrder() {
     if (isDexType()) {
       return 1;
diff --git a/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java
index 44c94c4..ab054e2 100644
--- a/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
+import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.MethodResolutionResult.FailedResolutionResult;
 import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
@@ -279,14 +280,7 @@
               bridgesToRemoveForClass.add(method);
 
               // Rewrite invokes to the bridge to the target if it is accessible.
-              // TODO(b/245882297): Refine these visibility checks so that we also rewrite when
-              //  the target is not public, but still accessible to call sites.
-              boolean isEligibleForRetargeting =
-                  redundantBridgeRemovalOptions.isRetargetingOfConstructorBridgeCallsEnabled()
-                      || !method.getDefinition().isInstanceInitializer();
-              if (isEligibleForRetargeting
-                  && target.getAccessFlags().isPublic()
-                  && target.getHolder().isPublic()) {
+              if (canRetargetInvokesToTargetMethod(method, target)) {
                 lensBuilder.map(method, target);
               }
             }
@@ -297,6 +291,39 @@
       }
     }
 
+    private boolean canRetargetInvokesToTargetMethod(
+        ProgramMethod method, DexClassAndMethod target) {
+      // Check if constructor retargeting is enabled.
+      if (method.getDefinition().isInstanceInitializer()
+          && !redundantBridgeRemovalOptions.isRetargetingOfConstructorBridgeCallsEnabled()) {
+        return false;
+      }
+      // Check if all possible contexts that have access to the holder of the redundant bridge
+      // method also have access to the holder of the target method.
+      DexProgramClass methodHolder = method.getHolder();
+      DexClass targetHolder = target.getHolder();
+      if (!targetHolder.getAccessFlags().isPublic()) {
+        if (methodHolder.getAccessFlags().isPublic() || !method.isSamePackage(target)) {
+          return false;
+        }
+      }
+      // Check if all possible contexts that have access to the redundant bridge method also have
+      // access to the target method.
+      if (target.getAccessFlags().isPublic()) {
+        return true;
+      }
+      MethodAccessFlags methodAccessFlags = method.getAccessFlags();
+      MethodAccessFlags targetAccessFlags = target.getAccessFlags();
+      if (methodAccessFlags.isPackagePrivate()
+          && !targetAccessFlags.isPrivate()
+          && method.isSamePackage(target)) {
+        return true;
+      }
+      return methodAccessFlags.isProtected()
+          && targetAccessFlags.isProtected()
+          && method.isSamePackage(target);
+    }
+
     @Override
     public void prune(DexProgramClass clazz) {
       // Empty.