From ac244bc17bece741fcca5d1b492d3c7f3764019b Mon Sep 17 00:00:00 2001 From: Fredrik Hubinette Date: Fri, 13 Dec 2024 17:24:13 -0800 Subject: [PATCH] Added HorizonOSQualityEnforcer to handle finish task command from thhe browser to shutdown TWA LauncherActivity --- .../trusted/HorizonOSQualityEnforcer.java | 92 +++++++++++++++++++ .../trusted/LauncherActivity.java | 7 +- 2 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 androidbrowserhelper/src/main/java/com/google/androidbrowserhelper/trusted/HorizonOSQualityEnforcer.java diff --git a/androidbrowserhelper/src/main/java/com/google/androidbrowserhelper/trusted/HorizonOSQualityEnforcer.java b/androidbrowserhelper/src/main/java/com/google/androidbrowserhelper/trusted/HorizonOSQualityEnforcer.java new file mode 100644 index 00000000..5c4b9be3 --- /dev/null +++ b/androidbrowserhelper/src/main/java/com/google/androidbrowserhelper/trusted/HorizonOSQualityEnforcer.java @@ -0,0 +1,92 @@ +// Copyright (c) Meta Platforms, Inc. and affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.androidbrowserhelper.trusted; + +import android.app.Activity; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.browser.customtabs.CustomTabsCallback; + +/** + * This class handles the quality enforcement messages from the browser to enforce the quality bar + * on the websites shown inside Trusted Web Activities. + * Browser should send a message when violation occurs. For example if a link from a page on the + * verified origin 404s or if a page cannot be displayed while offline. + * + * The purpose of this is to bring TWAs in line with native applications - if a native application + * tries to start an Activity that doesn't exist, it will crash. We should hold web apps to the + * same standard. + */ +public class HorizonOSQualityEnforcer extends CustomTabsCallback { + private static final String TAG = "HzOSTwaQualityEnforcer"; + static final String CRASH = "quality_enforcement.crash"; + static final String FINISH_TASK_COMMAND_NAME = "finishAndRemoveTask"; + static final String KEY_CRASH_REASON = "crash_reason"; + static final String KEY_SUCCESS = "success"; + + private final Delegate mDelegate; + private Activity mActivity; + + /** + * A Delegate interface that provides implementations for handling quality enforcement messages. + */ + interface Delegate { + /* Handling the CRASH message from browser. */ + void crash(String message); + } + + /* Constructor to be used in prod that throws a RuntimeException. */ + public HorizonOSQualityEnforcer() { + mDelegate = (message) -> { + // Put the exception in a looper so that the crash will not prevent returning the + // execution result to browser. + new Handler(Looper.getMainLooper()).post(() -> { + throw new RuntimeException(message); + }); + }; + } + + public HorizonOSQualityEnforcer(Activity activity) { + this(); + mActivity = activity; + } + + /* Constructor for use in tests. */ + HorizonOSQualityEnforcer(Delegate delegate) { mDelegate = delegate; } + + @Nullable + @Override + public Bundle extraCallbackWithResult( + @NonNull String callbackName, @Nullable Bundle args) { + Bundle result = new Bundle(); + if (callbackName.equals(CRASH)) { + result.putBoolean(KEY_SUCCESS, true); + String message = (args != null) ? args.getString(KEY_CRASH_REASON) : null; + if (message == null) return Bundle.EMPTY; + mDelegate.crash(message); + } else if (callbackName.equals(FINISH_TASK_COMMAND_NAME)) { + Log.d(TAG, "Received finishAndRemoveTask command from browser."); + if (mActivity != null) { + mActivity.finish(); + } + } + return result; + } +}; diff --git a/androidbrowserhelper/src/main/java/com/google/androidbrowserhelper/trusted/LauncherActivity.java b/androidbrowserhelper/src/main/java/com/google/androidbrowserhelper/trusted/LauncherActivity.java index c3237af7..9486a7fc 100644 --- a/androidbrowserhelper/src/main/java/com/google/androidbrowserhelper/trusted/LauncherActivity.java +++ b/androidbrowserhelper/src/main/java/com/google/androidbrowserhelper/trusted/LauncherActivity.java @@ -243,7 +243,12 @@ protected void launchTwa() { } protected CustomTabsCallback getCustomTabsCallback() { - return new QualityEnforcer(); + if (mMetadata.horizonOSAppMode != null) { + Log.d(TAG, "Creating HorizonOSQualityEnforcer."); + return new HorizonOSQualityEnforcer(this); + } else { + return new QualityEnforcer(); + } } protected TwaLauncher createTwaLauncher() {