Commit 360e54d9 authored by Robbie McElrath's avatar Robbie McElrath Committed by Commit Bot

Reland "[WebLayer] Change the return type of Fragment.getActivity() to Activity."

This is a reland of ae52b2a4 with fixes
for robolectric tests, which run on the JVM which appears to have
stricter verification checks.

Original change's description:
> [WebLayer] Change the return type of Fragment.getActivity() to Activity.
>
> This CL adds the ability to specify a script in an android_aar_prebuilt
> rule that can modify prebuilt jar files, and adds a script to change
> Fragment.getActivity() and Fragment.requireActivity() to return an
> Activity instead of a FragmentActivity.
>
> This is the first CL in a chain that will allow us to remove the fake
> activity we create when embedding Fragments that cross classloader
> boundaries.
>
> Bug: 1123216
> Change-Id: I4b9d3ca5f9c3a4d86e08d64f49d601c08fca9a70
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2432413
> Reviewed-by: Theresa  <twellington@chromium.org>
> Reviewed-by: Andrew Grieve <agrieve@chromium.org>
> Commit-Queue: Robbie McElrath <rmcelrath@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#823582}

Bug: 1123216
Change-Id: I83ed0c47cda12e4a71ed90cc0bce4cde2a07782d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2521343Reviewed-by: default avatarAndrew Grieve <agrieve@chromium.org>
Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Commit-Queue: Robbie McElrath <rmcelrath@chromium.org>
Cr-Commit-Position: refs/heads/master@{#826944}
parent 87d504a0
......@@ -1306,6 +1306,7 @@ _GENERIC_PYDEPS_FILES = [
'build/android/gyp/apkbuilder.pydeps',
'build/android/gyp/assert_static_initializers.pydeps',
'build/android/gyp/bytecode_processor.pydeps',
'build/android/gyp/bytecode_rewriter.pydeps',
'build/android/gyp/compile_java.pydeps',
'build/android/gyp/compile_resources.pydeps',
'build/android/gyp/copy_ex.pydeps',
......
......@@ -18,3 +18,17 @@ java_binary("bytecode_processor") {
wrapper_script_name = "helper/bytecode_processor"
enable_bytecode_checks = false
}
java_binary("fragment_activity_replacer") {
sources = [
"java/org/chromium/bytecode/ByteCodeRewriter.java",
"java/org/chromium/bytecode/FragmentActivityReplacer.java",
]
main_class = "org.chromium.bytecode.FragmentActivityReplacer"
deps = [
"//third_party/android_deps:org_ow2_asm_asm_commons_java",
"//third_party/android_deps:org_ow2_asm_asm_java",
"//third_party/android_deps:org_ow2_asm_asm_util_java",
]
wrapper_script_name = "helper/fragment_activity_replacer"
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.bytecode;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
/**
* Base class for scripts that perform bytecode modifications on a jar file.
*/
public abstract class ByteCodeRewriter {
private static final String CLASS_FILE_SUFFIX = ".class";
public void rewrite(File inputJar, File outputJar) throws IOException {
if (!inputJar.exists()) {
throw new FileNotFoundException("Input jar not found: " + inputJar.getPath());
}
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(inputJar))) {
try (OutputStream outputStream = new FileOutputStream(outputJar)) {
processZip(inputStream, outputStream);
}
}
}
/** Returns true if the class at the given path in the archive should be rewritten. */
protected abstract boolean shouldRewriteClass(String classPath);
/**
* Returns the ClassVisitor that should be used to modify the bytecode of class at the given
* path in the archive.
*/
protected abstract ClassVisitor getClassVisitorForClass(
String classPath, ClassVisitor delegate);
private void processZip(InputStream inputStream, OutputStream outputStream) {
try (ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) {
ZipInputStream zipInputStream = new ZipInputStream(inputStream);
ZipEntry entry;
while ((entry = zipInputStream.getNextEntry()) != null) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
boolean handled = processClassEntry(entry, zipInputStream, buffer);
if (handled) {
ZipEntry newEntry = new ZipEntry(entry.getName());
zipOutputStream.putNextEntry(newEntry);
zipOutputStream.write(buffer.toByteArray(), 0, buffer.size());
} else {
zipOutputStream.putNextEntry(entry);
zipInputStream.transferTo(zipOutputStream);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private boolean processClassEntry(
ZipEntry entry, InputStream inputStream, OutputStream outputStream) {
if (!entry.getName().endsWith(CLASS_FILE_SUFFIX) || !shouldRewriteClass(entry.getName())) {
return false;
}
try {
ClassReader reader = new ClassReader(inputStream);
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES);
ClassVisitor classVisitor = getClassVisitorForClass(entry.getName(), writer);
reader.accept(classVisitor, ClassReader.EXPAND_FRAMES);
writer.visitEnd();
byte[] classData = writer.toByteArray();
outputStream.write(classData, 0, classData.length);
return true;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.bytecode;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.MethodRemapper;
import org.objectweb.asm.commons.Remapper;
import java.io.File;
import java.io.IOException;
/**
* Java application that modifies Fragment.getActivity() to return an Activity instead of a
* FragmentActivity, and updates any existing getActivity() calls to reference the updated method.
*
* See crbug.com/1144345 for more context.
*/
public class FragmentActivityReplacer extends ByteCodeRewriter {
private static final String FRAGMENT_CLASS_PATH = "androidx/fragment/app/Fragment.class";
private static final String FRAGMENT_ACTIVITY_INTERNAL_CLASS_NAME =
"androidx/fragment/app/FragmentActivity";
private static final String ACTIVITY_INTERNAL_CLASS_NAME = "android/app/Activity";
private static final String GET_ACTIVITY_METHOD_NAME = "getActivity";
private static final String REQUIRE_ACTIVITY_METHOD_NAME = "requireActivity";
private static final String OLD_METHOD_DESCRIPTOR =
"()Landroidx/fragment/app/FragmentActivity;";
private static final String NEW_METHOD_DESCRIPTOR = "()Landroid/app/Activity;";
public static void main(String[] args) throws IOException {
// Invoke this script using //build/android/gyp/bytecode_processor.py
if (args.length != 2) {
System.err.println("Expected 2 arguments: [input.jar] [output.jar]");
System.exit(1);
}
FragmentActivityReplacer rewriter = new FragmentActivityReplacer();
rewriter.rewrite(new File(args[0]), new File(args[1]));
}
@Override
protected boolean shouldRewriteClass(String classPath) {
return true;
}
@Override
protected ClassVisitor getClassVisitorForClass(String classPath, ClassVisitor delegate) {
ClassVisitor getActivityReplacer = new GetActivityReplacer(delegate);
if (classPath.equals(FRAGMENT_CLASS_PATH)) {
return new FragmentClassVisitor(getActivityReplacer);
}
return getActivityReplacer;
}
/** Updates any Fragment.getActivity/requireActivity() calls to call the replaced method. */
private static class GetActivityReplacer extends ClassVisitor {
private GetActivityReplacer(ClassVisitor baseVisitor) {
super(Opcodes.ASM7, baseVisitor);
}
@Override
public MethodVisitor visitMethod(
int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor base = super.visitMethod(access, name, descriptor, signature, exceptions);
return new MethodVisitor(Opcodes.ASM7, base) {
@Override
public void visitMethodInsn(int opcode, String owner, String name,
String descriptor, boolean isInterface) {
if ((opcode == Opcodes.INVOKEVIRTUAL || opcode == Opcodes.INVOKESPECIAL)
&& descriptor.equals(OLD_METHOD_DESCRIPTOR)
&& (name.equals(GET_ACTIVITY_METHOD_NAME)
|| name.equals(REQUIRE_ACTIVITY_METHOD_NAME))) {
super.visitMethodInsn(
opcode, owner, name, NEW_METHOD_DESCRIPTOR, isInterface);
} else {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
}
};
}
}
/**
* Makes Fragment.getActivity() and Fragment.requireActivity() non-final, and changes their
* return types to Activity.
*/
private static class FragmentClassVisitor extends ClassVisitor {
private FragmentClassVisitor(ClassVisitor baseVisitor) {
super(Opcodes.ASM7, baseVisitor);
}
@Override
public MethodVisitor visitMethod(
int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor base;
// Update the descriptor of getActivity/requireActivity, and make them non-final.
if (name.equals(GET_ACTIVITY_METHOD_NAME)
|| name.equals(REQUIRE_ACTIVITY_METHOD_NAME)) {
base = super.visitMethod(
access & ~Opcodes.ACC_FINAL, name, NEW_METHOD_DESCRIPTOR, null, exceptions);
} else {
base = super.visitMethod(access, name, descriptor, signature, exceptions);
}
return new MethodRemapper(base, new Remapper() {
@Override
public String mapType(String internalName) {
if (internalName.equals(FRAGMENT_ACTIVITY_INTERNAL_CLASS_NAME)) {
return ACTIVITY_INTERNAL_CLASS_NAME;
}
return internalName;
}
});
}
}
}
#!/usr/bin/env python
# Copyright 2020 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Wrapper script around ByteCodeRewriter subclass scripts."""
import argparse
import sys
from util import build_utils
def main(argv):
argv = build_utils.ExpandFileArgs(argv[1:])
parser = argparse.ArgumentParser()
build_utils.AddDepfileOption(parser)
parser.add_argument('--script',
required=True,
help='Path to the java binary wrapper script.')
parser.add_argument('--classpath', action='append', nargs='+')
parser.add_argument('--input-jar', required=True)
parser.add_argument('--output-jar', required=True)
args = parser.parse_args(argv)
classpath = build_utils.ParseGnList(args.classpath)
build_utils.WriteDepfile(args.depfile, args.output_jar, inputs=classpath)
classpath.append(args.input_jar)
cmd = [
args.script, '--classpath', ':'.join(classpath), args.input_jar,
args.output_jar
]
build_utils.CheckOutput(cmd, print_stdout=True)
if __name__ == '__main__':
sys.exit(main(sys.argv))
# Generated by running:
# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/bytecode_rewriter.pydeps build/android/gyp/bytecode_rewriter.py
../../gn_helpers.py
bytecode_rewriter.py
util/__init__.py
util/build_utils.py
......@@ -3982,8 +3982,56 @@ if (enable_java_templates) {
}
} # _has_sources
# TODO(crbug.com/1123216): Implement bytecode_rewriter_target.
not_needed(invoker, [ "bytecode_rewriter_target" ])
if (_is_prebuilt || _build_device_jar || _build_host_jar) {
_unprocessed_jar_deps = _full_classpath_deps
if (_has_sources) {
_unprocessed_jar_deps += [ ":$_compile_java_target" ]
}
}
if (defined(invoker.bytecode_rewriter_target)) {
assert(_build_host_jar || _build_device_jar,
"A host or device jar must be created to use bytecode rewriting")
_rewritten_jar =
string_replace(_unprocessed_jar_path, ".jar", "_rewritten.jar")
_rewritten_jar_target_name = "${target_name}__rewritten"
_rewriter_path = root_build_dir + "/bin/helper/" +
get_label_info(invoker.bytecode_rewriter_target, "name")
_rebased_build_config = rebase_path(_build_config, root_build_dir)
action_with_pydeps(_rewritten_jar_target_name) {
script = "//build/android/gyp/bytecode_rewriter.py"
inputs = [
_rewriter_path,
_build_config,
_unprocessed_jar_path,
]
outputs = [ _rewritten_jar ]
depfile = "$target_gen_dir/$target_name.d"
args = [
"--depfile",
rebase_path(depfile, root_build_dir),
"--script",
rebase_path(_rewriter_path, root_build_dir),
"--classpath",
"@FileArg($_rebased_build_config:deps_info:javac_full_classpath)",
"--classpath",
"@FileArg($_rebased_build_config:android:sdk_jars)",
"--input-jar",
rebase_path(_unprocessed_jar_path, root_build_dir),
"--output-jar",
rebase_path(_rewritten_jar, root_build_dir),
]
deps = _unprocessed_jar_deps + [
":$_build_config_target_name",
invoker.bytecode_rewriter_target,
]
}
_unprocessed_jar_deps = []
_unprocessed_jar_deps = [ ":$_rewritten_jar_target_name" ]
_unprocessed_jar_path = _rewritten_jar
}
if (_is_prebuilt) {
generate_interface_jar(_header_target_name) {
......@@ -3998,10 +4046,7 @@ if (enable_java_templates) {
# target. If we can change compile & desugar steps to use direct
# interface classpath rather than full interface classpath, then this
# could just be _non_java_deps.
deps = _classpath_deps
if (_has_sources) {
deps += [ ":$_compile_java_target" ]
}
deps = _unprocessed_jar_deps
}
_public_deps += [ ":$_header_target_name" ]
}
......@@ -4017,10 +4062,7 @@ if (enable_java_templates) {
build_config = _build_config
build_config_dep = ":$_build_config_target_name"
input_jar_path = _unprocessed_jar_path
jar_deps = _non_java_deps
if (_has_sources) {
jar_deps += [ ":$_compile_java_target" ]
}
jar_deps = _unprocessed_jar_deps
if (_build_host_jar) {
host_jar_path = _host_processed_jar_path
}
......@@ -4063,10 +4105,7 @@ if (enable_java_templates) {
_bytecode_checks_target = "${target_name}__validate_classpath"
bytecode_processor(_bytecode_checks_target) {
forward_variables_from(invoker, [ "missing_classes_allowlist" ])
deps = _full_classpath_deps
if (_has_sources) {
deps += [ ":$_compile_java_target" ]
}
deps = _unprocessed_jar_deps + [ ":$_build_config_target_name" ]
requires_android = _requires_android
target_label =
get_label_info(":${invoker.target_name}", "label_no_toolchain")
......
......@@ -4,7 +4,7 @@
package org.chromium.chrome.browser.firstrun;
import androidx.fragment.app.FragmentActivity;
import android.app.Activity;
/**
* This interface is implemented by FRE fragments.
......@@ -25,7 +25,7 @@ public interface FirstRunFragment {
/**
* @see Fragment#getActivity().
*/
FragmentActivity getActivity();
Activity getActivity();
/**
* Set the a11y focus when the fragment is shown on the screen.
......
......@@ -141,8 +141,7 @@ public class PasswordEntryEditor extends Fragment {
}
mPendingAction = action;
ReauthenticationManager.displayReauthenticationFragment(reasonString, View.NO_ID,
getActivity().getSupportFragmentManager(),
ReauthenticationManager.ReauthScope.ONE_AT_A_TIME);
getParentFragmentManager(), ReauthenticationManager.ReauthScope.ONE_AT_A_TIME);
}
@Override
......@@ -150,4 +149,4 @@ public class PasswordEntryEditor extends Fragment {
super.onDestroy();
PasswordEditingDelegateProvider.getInstance().getPasswordEditingDelegate().destroy();
}
}
\ No newline at end of file
}
......@@ -84,8 +84,7 @@ public class CableAuthenticatorModuleProvider extends Fragment {
private void showModule() {
mStatus.setText("Installed.");
FragmentTransaction transaction =
getActivity().getSupportFragmentManager().beginTransaction();
FragmentTransaction transaction = getParentFragmentManager().beginTransaction();
Fragment fragment = Cablev2AuthenticatorModule.getImpl().getFragment();
Bundle arguments = getArguments();
if (arguments == null) {
......
......@@ -366,6 +366,9 @@ android_aar_prebuilt("androidx_fragment_fragment_java") {
jar_excluded_patterns = [ "androidx/fragment/app/DialogFragment*" ]
ignore_proguard_configs = true
bytecode_rewriter_target =
"//build/android/bytecode:fragment_activity_replacer"
}
# This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
......@@ -577,6 +580,8 @@ android_aar_prebuilt("androidx_preference_preference_java") {
"androidx/preference/PreferenceFragmentCompat*",
]
bytecode_rewriter_target =
"//build/android/bytecode:fragment_activity_replacer"
ignore_proguard_configs = true
}
......
......@@ -354,6 +354,8 @@ class BuildConfigGenerator extends DefaultTask {
| ]
|
| ignore_proguard_configs = true
|
| bytecode_rewriter_target = "//build/android/bytecode:fragment_activity_replacer"
|""".stripMargin())
break
case 'androidx_media_media':
......@@ -496,6 +498,7 @@ class BuildConfigGenerator extends DefaultTask {
| "androidx/preference/PreferenceFragmentCompat*",
| ]
|
| bytecode_rewriter_target = "//build/android/bytecode:fragment_activity_replacer"
|""".stripMargin())
// Replace broad library -keep rules with a more limited set in
// chrome/android/java/proguard.flags instead.
......
This directory contains PreferenceFragmentCompat.java and
PreferenceDialogFragmentCompat.java, copied without changes from the AndroidX
preference library at commit beeb6fb. These files contain two changes (commits
72c0381 and beeb6fb) that we want in Chromium, but are not yet in an official
preference library release.
preference library at commit e865a9b. These files contain three changes
(commits 72c0381, beeb6fb, and e865a9b) that we want in Chromium, but are not
yet in an official preference library release.
To pull in these changes, we exclude PreferenceFragmentCompat and
PreferenceDialogFragmentCompat from the androidx_preference_preference library
......
......@@ -61,7 +61,7 @@ import androidx.recyclerview.widget.RecyclerView;
*
* <p>To build a hierarchy from code, use
* {@link PreferenceManager#createPreferenceScreen(Context)} to create the root
* {@link PreferenceScreen}. Once you have added other {@link Preference}s to this root scree
* {@link PreferenceScreen}. Once you have added other {@link Preference}s to this root screen
* with {@link PreferenceScreen#addPreference(Preference)}, you then need to set the screen as
* the root screen in your hierarchy with {@link #setPreferenceScreen(PreferenceScreen)}.
*
......@@ -420,8 +420,7 @@ public abstract class PreferenceFragmentCompat extends Fragment implements
+ "implement this method so that you can configure the new "
+ "fragment that will be displayed, and set a transition between "
+ "the fragments.");
final FragmentManager fragmentManager = requireActivity()
.getSupportFragmentManager();
final FragmentManager fragmentManager = getParentFragmentManager();
final Bundle args = preference.getExtras();
final Fragment fragment = fragmentManager.getFragmentFactory().instantiate(
requireActivity().getClassLoader(), preference.getFragment());
......@@ -457,7 +456,7 @@ public abstract class PreferenceFragmentCompat extends Fragment implements
.onPreferenceStartScreen(this, preferenceScreen);
}
if (!handled && getContext() instanceof OnPreferenceStartScreenCallback) {
((OnPreferenceStartScreenCallback) getContext())
handled = ((OnPreferenceStartScreenCallback) getContext())
.onPreferenceStartScreen(this, preferenceScreen);
}
// Check the Activity as well in case getContext was overridden to return something other
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment