Commit ae52b2a4 authored by Robbie McElrath's avatar Robbie McElrath Committed by Commit Bot

[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/+/2432413Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Reviewed-by: default avatarAndrew Grieve <agrieve@chromium.org>
Commit-Queue: Robbie McElrath <rmcelrath@chromium.org>
Cr-Commit-Position: refs/heads/master@{#823582}
parent ffa5ac9b
......@@ -1304,6 +1304,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 && 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) {
if (!name.equals(GET_ACTIVITY_METHOD_NAME)
&& !name.equals(REQUIRE_ACTIVITY_METHOD_NAME)) {
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
return new MethodRemapper(super.visitMethod(access & ~Opcodes.ACC_FINAL, name,
NEW_METHOD_DESCRIPTOR, null, exceptions),
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
......@@ -3921,6 +3921,57 @@ if (enable_java_templates) {
}
} # _has_sources
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) {
# Always used the unfiltered .jar to create the interface jar so that
......@@ -3934,10 +3985,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" ]
}
......@@ -3953,10 +4001,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
}
......@@ -3999,10 +4044,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")
......
......@@ -4356,6 +4356,7 @@ if (enable_java_templates) {
# Create android_java_prebuilt target for classes.jar.
if (_scanned_files.has_classes_jar) {
_java_library_vars = [
"bytecode_rewriter_target",
"enable_bytecode_checks",
"enable_jetify",
"jar_excluded_patterns",
......
......@@ -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
......
......@@ -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) {
......
......@@ -332,6 +332,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.
......@@ -543,6 +546,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':
......@@ -494,6 +496,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