[Android] Fix in-app review flow crash on Play Store split APK installs (#34229)
PlayCoreDialogWrapperActivity (from core-common) lives in base.apk but deserializes a Parcelable (review.zzc) from split_chrome.apk. The base split's ClassLoader can't see chrome split classes, causing ClassNotFoundException. Chromium fixes this for its own activities via BundleUtils.checkContextClassLoader(), but PlayCoreDialogWrapperActivity extends plain Activity and is not covered. Fix by registering an ActivityLifecycleCallbacks that sets the Application's ClassLoader on the Intent extras Bundle before onCreate(). Also adds a QA preference to force the in-app review flow from settings for easier testing. Resolves: https://github.com/brave/brave-browser/issues/53179
This commit is contained in:
@@ -1,14 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) 2022 The Brave Authors. All rights reserved.
|
||||
/* Copyright (c) 2022 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
package org.chromium.chrome.browser;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.wireguard.android.backend.GoBackend;
|
||||
|
||||
import org.chromium.build.annotations.NullMarked;
|
||||
import org.chromium.build.annotations.Nullable;
|
||||
import org.chromium.chrome.browser.base.SplitCompatApplication;
|
||||
import org.chromium.chrome.browser.vpn.utils.BraveVpnProfileUtils;
|
||||
import org.chromium.components.safe_browsing.BraveSafeBrowsingApiHandler;
|
||||
@@ -30,6 +34,71 @@ public class BraveApplicationImplBase extends SplitCompatApplication.Impl {
|
||||
// Set a handler for SafeBrowsing. It has to be done only once for a process lifetime.
|
||||
SafeBrowsingApiBridge.setSafeBrowsingApiHandler(
|
||||
BraveSafeBrowsingApiHandler.getInstance());
|
||||
|
||||
// Fix ClassNotFoundException crash in Play Core's in-app review flow.
|
||||
//
|
||||
// When the app is installed from Google Play as split APKs (AAB), each split
|
||||
// gets its own ClassLoader. PlayCoreDialogWrapperActivity (from core-common)
|
||||
// lives in base.apk, but the Parcelable it deserializes from its Intent extras
|
||||
// (com.google.android.play.core.review.zzc, a ResultReceiver subclass from the
|
||||
// review library) lives in split_chrome.apk. When the activity calls
|
||||
// getIntent().getParcelableExtra("result_receiver") in onCreate(), the Bundle
|
||||
// uses the base split's ClassLoader which cannot see chrome split classes,
|
||||
// causing BadParcelableException -> ClassNotFoundException.
|
||||
//
|
||||
// Chromium has the same class of bug for its own activities and fixes it in
|
||||
// ChromeBaseAppCompatActivity.attachBaseContext() via
|
||||
// BundleUtils.checkContextClassLoader() (see https://crbug.com/346709145).
|
||||
// However, PlayCoreDialogWrapperActivity extends plain android.app.Activity,
|
||||
// not ChromeBaseAppCompatActivity, so it never receives that fix.
|
||||
//
|
||||
// This callback fires before onCreate() and sets the Intent Bundle's
|
||||
// ClassLoader to the Application's ClassLoader (which points to
|
||||
// split_chrome.apk and can resolve all app classes). This is not reproducible
|
||||
// with local APK installs — only with split APK delivery (Play Store or
|
||||
// bundletool --local-testing).
|
||||
getApplication()
|
||||
.registerActivityLifecycleCallbacks(
|
||||
new Application.ActivityLifecycleCallbacks() {
|
||||
@Override
|
||||
public void onActivityPreCreated(
|
||||
Activity activity, @Nullable Bundle savedInstanceState) {
|
||||
if (activity.getClass()
|
||||
.getName()
|
||||
.equals(
|
||||
"com.google.android.play.core.common"
|
||||
+ ".PlayCoreDialogWrapperActivity")) {
|
||||
Intent intent = activity.getIntent();
|
||||
if (intent != null) {
|
||||
intent.setExtrasClassLoader(
|
||||
getApplication().getClassLoader());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(
|
||||
Activity activity, @Nullable Bundle savedInstanceState) {}
|
||||
|
||||
@Override
|
||||
public void onActivityStarted(Activity activity) {}
|
||||
|
||||
@Override
|
||||
public void onActivityResumed(Activity activity) {}
|
||||
|
||||
@Override
|
||||
public void onActivityPaused(Activity activity) {}
|
||||
|
||||
@Override
|
||||
public void onActivityStopped(Activity activity) {}
|
||||
|
||||
@Override
|
||||
public void onActivitySaveInstanceState(
|
||||
Activity activity, Bundle outState) {}
|
||||
|
||||
@Override
|
||||
public void onActivityDestroyed(Activity activity) {}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.chromium.base.Log;
|
||||
import org.chromium.base.task.PostTask;
|
||||
import org.chromium.base.task.TaskTraits;
|
||||
import org.chromium.chrome.R;
|
||||
import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
|
||||
|
||||
public class BraveAskPlayStoreRatingDialog extends BottomSheetDialogFragment {
|
||||
public static final String TAG_FRAGMENT = "brave_ask_play_store_rating_dialog_tag";
|
||||
@@ -111,7 +112,9 @@ public class BraveAskPlayStoreRatingDialog extends BottomSheetDialogFragment {
|
||||
rateNowButton.setOnClickListener(
|
||||
(v) -> {
|
||||
try {
|
||||
if (mIsFromSettings) {
|
||||
if (mIsFromSettings
|
||||
&& !ChromeSharedPreferences.getInstance()
|
||||
.readBoolean("qa_force_in_app_review", false)) {
|
||||
RateUtils.getInstance().openPlaystore(mContext);
|
||||
} else {
|
||||
launchReviewFlow();
|
||||
|
||||
Reference in New Issue
Block a user