Commit d944f249 authored by Peter Kotwicz's avatar Peter Kotwicz Committed by Commit Bot

[Android WebAPK] Add WebappCoordinator/WebApkCoordinator

This CL:
- Daggerizes:
  - WebappDisclosureSnackbarController
  - WebappActionsNotificationManager
- Moves the management of the following classes out of
  WebappActivity/WebApkActivity and into
  WebappActivityCoordinator/WebApkActivityCoordinator
  - WebappDisclosureSnackbarController
  - WebappActionsNotificationManager
  - WebApkUpdateManager
- Introduces new class - WebappDeferredStartupWithStorageRegistrar -
  for getting notified once the WebappDataStorage has been fetched
  (and potentially created) during deferred startup.

BUG=1042147
TEST=WebApkInitializationTest

Change-Id: I98ea759d4f1186da4e58d899aab6f3aec2caaecc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2005976
Commit-Queue: Peter Kotwicz <pkotwicz@chromium.org>
Reviewed-by: default avatarYaron Friedman <yfriedman@chromium.org>
Reviewed-by: default avatarGlenn Hartmann <hartmanng@chromium.org>
Cr-Commit-Position: refs/heads/master@{#742480}
parent 565cec0b
...@@ -1795,6 +1795,7 @@ chrome_java_sources = [ ...@@ -1795,6 +1795,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/webapps/WebApkActivity7.java", "java/src/org/chromium/chrome/browser/webapps/WebApkActivity7.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkActivity8.java", "java/src/org/chromium/chrome/browser/webapps/WebApkActivity8.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkActivity9.java", "java/src/org/chromium/chrome/browser/webapps/WebApkActivity9.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkActivityCoordinator.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkExtras.java", "java/src/org/chromium/chrome/browser/webapps/WebApkExtras.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkHandlerDelegate.java", "java/src/org/chromium/chrome/browser/webapps/WebApkHandlerDelegate.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkInfo.java", "java/src/org/chromium/chrome/browser/webapps/WebApkInfo.java",
...@@ -1825,10 +1826,12 @@ chrome_java_sources = [ ...@@ -1825,10 +1826,12 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/webapps/WebappActivity7.java", "java/src/org/chromium/chrome/browser/webapps/WebappActivity7.java",
"java/src/org/chromium/chrome/browser/webapps/WebappActivity8.java", "java/src/org/chromium/chrome/browser/webapps/WebappActivity8.java",
"java/src/org/chromium/chrome/browser/webapps/WebappActivity9.java", "java/src/org/chromium/chrome/browser/webapps/WebappActivity9.java",
"java/src/org/chromium/chrome/browser/webapps/WebappActivityCoordinator.java",
"java/src/org/chromium/chrome/browser/webapps/WebappActivityTabController.java", "java/src/org/chromium/chrome/browser/webapps/WebappActivityTabController.java",
"java/src/org/chromium/chrome/browser/webapps/WebappAuthenticator.java", "java/src/org/chromium/chrome/browser/webapps/WebappAuthenticator.java",
"java/src/org/chromium/chrome/browser/webapps/WebappCustomTabTimeSpentLogger.java", "java/src/org/chromium/chrome/browser/webapps/WebappCustomTabTimeSpentLogger.java",
"java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java", "java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java",
"java/src/org/chromium/chrome/browser/webapps/WebappDeferredStartupWithStorageHandler.java",
"java/src/org/chromium/chrome/browser/webapps/WebappDirectoryManager.java", "java/src/org/chromium/chrome/browser/webapps/WebappDirectoryManager.java",
"java/src/org/chromium/chrome/browser/webapps/WebappDisclosureSnackbarController.java", "java/src/org/chromium/chrome/browser/webapps/WebappDisclosureSnackbarController.java",
"java/src/org/chromium/chrome/browser/webapps/WebappExtras.java", "java/src/org/chromium/chrome/browser/webapps/WebappExtras.java",
......
...@@ -517,6 +517,7 @@ chrome_test_java_sources = [ ...@@ -517,6 +517,7 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/webapps/ActivityAssignerTest.java", "javatests/src/org/chromium/chrome/browser/webapps/ActivityAssignerTest.java",
"javatests/src/org/chromium/chrome/browser/webapps/WebApkActivityTest.java", "javatests/src/org/chromium/chrome/browser/webapps/WebApkActivityTest.java",
"javatests/src/org/chromium/chrome/browser/webapps/WebApkActivityTestRule.java", "javatests/src/org/chromium/chrome/browser/webapps/WebApkActivityTestRule.java",
"javatests/src/org/chromium/chrome/browser/webapps/WebApkInitializationTest.java",
"javatests/src/org/chromium/chrome/browser/webapps/WebApkIntegrationTest.java", "javatests/src/org/chromium/chrome/browser/webapps/WebApkIntegrationTest.java",
"javatests/src/org/chromium/chrome/browser/webapps/WebApkUpdateDataFetcherTest.java", "javatests/src/org/chromium/chrome/browser/webapps/WebApkUpdateDataFetcherTest.java",
"javatests/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerTest.java", "javatests/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerTest.java",
......
...@@ -366,7 +366,7 @@ public abstract class ChromeActivity<C extends ChromeActivityComponent> ...@@ -366,7 +366,7 @@ public abstract class ChromeActivity<C extends ChromeActivityComponent>
ChromeActivityCommonsModule commonsModule = overridenCommonsFactory == null ChromeActivityCommonsModule commonsModule = overridenCommonsFactory == null
? new ChromeActivityCommonsModule(this, getLifecycleDispatcher()) ? new ChromeActivityCommonsModule(this, getLifecycleDispatcher())
: overridenCommonsFactory.create(this); : overridenCommonsFactory.create(this, getLifecycleDispatcher());
return createComponent(commonsModule); return createComponent(commonsModule);
} }
......
...@@ -318,6 +318,13 @@ public class BrowserServicesIntentDataProvider { ...@@ -318,6 +318,13 @@ public class BrowserServicesIntentDataProvider {
return false; return false;
} }
/**
* @return Whether the Activity is a WebAPK activity.
*/
public boolean isWebApkActivity() {
return false;
}
/** /**
* @return Whether the Activity should attempt to load a dynamic module. * @return Whether the Activity should attempt to load a dynamic module.
* *
......
...@@ -29,6 +29,7 @@ import org.chromium.chrome.browser.tabmodel.ChromeTabCreator; ...@@ -29,6 +29,7 @@ import org.chromium.chrome.browser.tabmodel.ChromeTabCreator;
import org.chromium.chrome.browser.tabmodel.TabModelSelector; import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorImpl; import org.chromium.chrome.browser.tabmodel.TabModelSelectorImpl;
import org.chromium.chrome.browser.ui.RootUiCoordinator; import org.chromium.chrome.browser.ui.RootUiCoordinator;
import org.chromium.chrome.browser.webapps.WebappActivityCoordinator;
/** /**
* Contains functionality which is shared between {@link WebappActivity} and * Contains functionality which is shared between {@link WebappActivity} and
...@@ -44,6 +45,7 @@ public abstract class BaseCustomTabActivity<C extends BaseCustomTabActivityCompo ...@@ -44,6 +45,7 @@ public abstract class BaseCustomTabActivity<C extends BaseCustomTabActivityCompo
protected CustomTabToolbarColorController mToolbarColorController; protected CustomTabToolbarColorController mToolbarColorController;
protected CustomTabStatusBarColorProvider mStatusBarColorProvider; protected CustomTabStatusBarColorProvider mStatusBarColorProvider;
protected CustomTabActivityTabFactory mTabFactory; protected CustomTabActivityTabFactory mTabFactory;
protected @Nullable WebappActivityCoordinator mWebappActivityCoordinator;
// This is to give the right package name while using the client's resources during an // This is to give the right package name while using the client's resources during an
// overridePendingTransition call. // overridePendingTransition call.
...@@ -76,6 +78,14 @@ public abstract class BaseCustomTabActivity<C extends BaseCustomTabActivityCompo ...@@ -76,6 +78,14 @@ public abstract class BaseCustomTabActivity<C extends BaseCustomTabActivityCompo
component.resolveCompositorContentInitializer(); component.resolveCompositorContentInitializer();
component.resolveTaskDescriptionHelper(); component.resolveTaskDescriptionHelper();
BrowserServicesIntentDataProvider intentDataProvider = getIntentDataProvider();
if (intentDataProvider.isWebappOrWebApkActivity()) {
mWebappActivityCoordinator = component.resolveWebappActivityCoordinator();
}
if (intentDataProvider.isWebApkActivity()) {
component.resolveWebApkActivityCoordinator();
}
} }
/** /**
...@@ -210,6 +220,14 @@ public abstract class BaseCustomTabActivity<C extends BaseCustomTabActivityCompo ...@@ -210,6 +220,14 @@ public abstract class BaseCustomTabActivity<C extends BaseCustomTabActivityCompo
return mStatusBarColorProvider.getBaseStatusBarColor(tab, super.getBaseStatusBarColor(tab)); return mStatusBarColorProvider.getBaseStatusBarColor(tab, super.getBaseStatusBarColor(tab));
} }
@Override
public void initDeferredStartupForActivity() {
if (mWebappActivityCoordinator != null) {
mWebappActivityCoordinator.initDeferredStartupForActivity();
}
super.initDeferredStartupForActivity();
}
@Override @Override
public boolean dispatchKeyEvent(KeyEvent event) { public boolean dispatchKeyEvent(KeyEvent event) {
Boolean result = KeyboardShortcuts.dispatchKeyEvent( Boolean result = KeyboardShortcuts.dispatchKeyEvent(
......
...@@ -17,6 +17,8 @@ import org.chromium.chrome.browser.customtabs.features.toolbar.CustomTabToolbarC ...@@ -17,6 +17,8 @@ import org.chromium.chrome.browser.customtabs.features.toolbar.CustomTabToolbarC
import org.chromium.chrome.browser.customtabs.features.toolbar.CustomTabToolbarCoordinator; import org.chromium.chrome.browser.customtabs.features.toolbar.CustomTabToolbarCoordinator;
import org.chromium.chrome.browser.dependency_injection.ChromeActivityComponent; import org.chromium.chrome.browser.dependency_injection.ChromeActivityComponent;
import org.chromium.chrome.browser.webapps.SplashController; import org.chromium.chrome.browser.webapps.SplashController;
import org.chromium.chrome.browser.webapps.WebApkActivityCoordinator;
import org.chromium.chrome.browser.webapps.WebappActivityCoordinator;
/** /**
* Contains accessors which are shared between {@link CustomTabActivityComponent} and * Contains accessors which are shared between {@link CustomTabActivityComponent} and
...@@ -35,4 +37,6 @@ public interface BaseCustomTabActivityComponent extends ChromeActivityComponent ...@@ -35,4 +37,6 @@ public interface BaseCustomTabActivityComponent extends ChromeActivityComponent
SplashController resolveSplashController(); SplashController resolveSplashController();
TabObserverRegistrar resolveTabObserverRegistrar(); TabObserverRegistrar resolveTabObserverRegistrar();
TwaFinishHandler resolveTwaFinishHandler(); TwaFinishHandler resolveTwaFinishHandler();
WebappActivityCoordinator resolveWebappActivityCoordinator();
WebApkActivityCoordinator resolveWebApkActivityCoordinator();
} }
...@@ -39,7 +39,10 @@ public class ChromeActivityCommonsModule { ...@@ -39,7 +39,10 @@ public class ChromeActivityCommonsModule {
private final ActivityLifecycleDispatcher mLifecycleDispatcher; private final ActivityLifecycleDispatcher mLifecycleDispatcher;
/** See {@link ModuleFactoryOverrides} */ /** See {@link ModuleFactoryOverrides} */
public interface Factory { ChromeActivityCommonsModule create(ChromeActivity<?> activity); } public interface Factory {
ChromeActivityCommonsModule create(ChromeActivity<?> activity,
ActivityLifecycleDispatcher activityLifecycleDispatcher);
}
public ChromeActivityCommonsModule( public ChromeActivityCommonsModule(
ChromeActivity<?> activity, ActivityLifecycleDispatcher lifecycleDispatcher) { ChromeActivity<?> activity, ActivityLifecycleDispatcher lifecycleDispatcher) {
......
...@@ -61,11 +61,6 @@ public class WebApkActivity extends WebappActivity { ...@@ -61,11 +61,6 @@ public class WebApkActivity extends WebappActivity {
&& !webApkPackageName.startsWith(WebApkConstants.WEBAPK_PACKAGE_PREFIX); && !webApkPackageName.startsWith(WebApkConstants.WEBAPK_PACKAGE_PREFIX);
} }
@Override
public String getWebApkPackageName() {
return getWebApkInfo().webApkPackageName();
}
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
...@@ -79,40 +74,6 @@ public class WebApkActivity extends WebappActivity { ...@@ -79,40 +74,6 @@ public class WebApkActivity extends WebappActivity {
RecordHistogram.recordTimesHistogram("MobileStartup.IntentToCreationTime.WebApk", timeMs); RecordHistogram.recordTimesHistogram("MobileStartup.IntentToCreationTime.WebApk", timeMs);
} }
@Override
protected void onDeferredStartupWithStorage(WebappDataStorage storage) {
super.onDeferredStartupWithStorage(storage);
WebApkInfo info = getWebApkInfo();
WebApkUma.recordShellApkVersion(info.shellApkVersion(), info.distributor());
storage.incrementLaunchCount();
getComponent().resolveWebApkUpdateManager().updateIfNeeded(storage, info);
}
@Override
protected void onDeferredStartupWithNullStorage(
WebappDisclosureSnackbarController disclosureSnackbarController) {
// WebappDataStorage objects are cleared if a user clears Chrome's data. Recreate them
// for WebAPKs since we need to store metadata for updates and disclosure notifications.
WebappRegistry.getInstance().register(
getWebApkInfo().id(), new WebappRegistry.FetchWebappDataStorageCallback() {
@Override
public void onWebappDataStorageRetrieved(WebappDataStorage storage) {
if (isActivityFinishingOrDestroyed()) return;
onDeferredStartupWithStorage(storage);
// Set force == true to indicate that we need to show a privacy
// disclosure for the newly installed unbound WebAPKs which
// have no storage yet. We can't simply default to a showing if the
// storage has a default value as we don't want to show this disclosure
// for pre-existing unbound WebAPKs.
disclosureSnackbarController.maybeShowDisclosure(
WebApkActivity.this, storage, true /* force */);
}
});
}
@Override @Override
public void onPauseWithNative() { public void onPauseWithNative() {
WebApkInfo info = getWebApkInfo(); WebApkInfo info = getWebApkInfo();
......
// 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.chrome.browser.webapps;
import androidx.annotation.Nullable;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.dependency_injection.ActivityScope;
import org.chromium.chrome.browser.metrics.WebApkUma;
import javax.inject.Inject;
import dagger.Lazy;
/**
* Coordinator for the WebAPK activity component.
* Add methods here if other components need to communicate with the WebAPK activity component.
*/
@ActivityScope
public class WebApkActivityCoordinator {
private final WebApkActivity mActivity;
private final Lazy<WebApkUpdateManager> mWebApkUpdateManager;
@Inject
public WebApkActivityCoordinator(ChromeActivity<?> activity,
WebappDeferredStartupWithStorageHandler deferredStartupWithStorageHandler,
WebappDisclosureSnackbarController disclosureSnackbarController,
Lazy<WebApkUpdateManager> webApkUpdateManager) {
// We don't need to do anything with |disclosureSnackbarController|. We just need to resolve
// it so that it starts working.
mActivity = (WebApkActivity) activity;
mWebApkUpdateManager = webApkUpdateManager;
deferredStartupWithStorageHandler.addTask((storage, didCreateStorage) -> {
if (activity.isActivityFinishingOrDestroyed()) return;
onDeferredStartupWithStorage(storage, didCreateStorage);
});
}
public void onDeferredStartupWithStorage(
@Nullable WebappDataStorage storage, boolean didCreateStorage) {
assert storage != null;
WebApkInfo info = mActivity.getWebApkInfo();
WebApkUma.recordShellApkVersion(info.shellApkVersion(), info.distributor());
storage.incrementLaunchCount();
mWebApkUpdateManager.get().updateIfNeeded(storage, info);
}
}
...@@ -75,6 +75,7 @@ public class WebApkUpdateDataFetcher extends EmptyTabObserver { ...@@ -75,6 +75,7 @@ public class WebApkUpdateDataFetcher extends EmptyTabObserver {
* Puts the object in a state where it is safe to be destroyed. * Puts the object in a state where it is safe to be destroyed.
*/ */
public void destroy() { public void destroy() {
if (mTab == null) return;
mTab.removeObserver(this); mTab.removeObserver(this);
WebApkUpdateDataFetcherJni.get().destroy(mNativePointer, WebApkUpdateDataFetcher.this); WebApkUpdateDataFetcherJni.get().destroy(mNativePointer, WebApkUpdateDataFetcher.this);
mNativePointer = 0; mNativePointer = 0;
......
...@@ -10,10 +10,17 @@ import android.content.Context; ...@@ -10,10 +10,17 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import androidx.annotation.NonNull;
import org.chromium.base.ContextUtils; import org.chromium.base.ContextUtils;
import org.chromium.base.metrics.RecordUserAction; import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.IntentHandler; import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.browserservices.BrowserServicesIntentDataProvider;
import org.chromium.chrome.browser.customtabs.content.CustomTabActivityTabProvider;
import org.chromium.chrome.browser.dependency_injection.ActivityScope;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.lifecycle.PauseResumeWithNativeObserver;
import org.chromium.chrome.browser.notifications.ChromeNotification; import org.chromium.chrome.browser.notifications.ChromeNotification;
import org.chromium.chrome.browser.notifications.NotificationBuilderFactory; import org.chromium.chrome.browser.notifications.NotificationBuilderFactory;
import org.chromium.chrome.browser.notifications.NotificationConstants; import org.chromium.chrome.browser.notifications.NotificationConstants;
...@@ -30,6 +37,8 @@ import org.chromium.ui.base.Clipboard; ...@@ -30,6 +37,8 @@ import org.chromium.ui.base.Clipboard;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import javax.inject.Inject;
/** /**
* Manages the notification shown by Chrome when running standalone Web Apps. It accomplishes * Manages the notification shown by Chrome when running standalone Web Apps. It accomplishes
* number of goals: * number of goals:
...@@ -37,7 +46,8 @@ import java.lang.ref.WeakReference; ...@@ -37,7 +46,8 @@ import java.lang.ref.WeakReference;
* - Exposes 'Share' and 'Open in Chrome' actions. * - Exposes 'Share' and 'Open in Chrome' actions.
* - Messages that Web App runs in Chrome. * - Messages that Web App runs in Chrome.
*/ */
class WebappActionsNotificationManager { @ActivityScope
class WebappActionsNotificationManager implements PauseResumeWithNativeObserver {
private static final String ACTION_SHARE = private static final String ACTION_SHARE =
"org.chromium.chrome.browser.webapps.NOTIFICATION_ACTION_SHARE"; "org.chromium.chrome.browser.webapps.NOTIFICATION_ACTION_SHARE";
private static final String ACTION_OPEN_IN_CHROME = private static final String ACTION_OPEN_IN_CHROME =
...@@ -45,16 +55,40 @@ class WebappActionsNotificationManager { ...@@ -45,16 +55,40 @@ class WebappActionsNotificationManager {
private static final String ACTION_FOCUS = private static final String ACTION_FOCUS =
"org.chromium.chrome.browser.webapps.NOTIFICATION_ACTION_FOCUS"; "org.chromium.chrome.browser.webapps.NOTIFICATION_ACTION_FOCUS";
public static void maybeShowNotification(Tab tab, WebappInfo webappInfo) { private final CustomTabActivityTabProvider mTabProvider;
if (tab == null) return; private final BrowserServicesIntentDataProvider mIntentDataProvider;
@Inject
public WebappActionsNotificationManager(CustomTabActivityTabProvider tabProvider,
BrowserServicesIntentDataProvider intentDataProvider,
ActivityLifecycleDispatcher lifecycleDispatcher) {
mTabProvider = tabProvider;
mIntentDataProvider = intentDataProvider;
lifecycleDispatcher.register(this);
}
@Override
public void onResumeWithNative() {
maybeShowNotification(mTabProvider.getTab(), mIntentDataProvider);
}
@Override
public void onPauseWithNative() {
cancelNotification();
}
private static void maybeShowNotification(
Tab tab, BrowserServicesIntentDataProvider intentDataProvider) {
WebappExtras webappExtras = intentDataProvider.getWebappExtras();
if (tab == null || webappExtras == null) return;
// All features provided by the notification are also available in the minimal-ui toolbar. // All features provided by the notification are also available in the minimal-ui toolbar.
if (webappInfo.displayMode() == WebDisplayMode.MINIMAL_UI) { if (webappExtras.displayMode == WebDisplayMode.MINIMAL_UI) {
return; return;
} }
Context appContext = ContextUtils.getApplicationContext(); Context appContext = ContextUtils.getApplicationContext();
ChromeNotification notification = createNotification(appContext, tab, webappInfo); ChromeNotification notification = createNotification(appContext, tab, webappExtras);
NotificationManagerProxy nm = new NotificationManagerProxyImpl(appContext); NotificationManagerProxy nm = new NotificationManagerProxyImpl(appContext);
nm.notify(notification); nm.notify(notification);
...@@ -64,7 +98,7 @@ class WebappActionsNotificationManager { ...@@ -64,7 +98,7 @@ class WebappActionsNotificationManager {
} }
private static ChromeNotification createNotification( private static ChromeNotification createNotification(
Context appContext, Tab tab, WebappInfo webappInfo) { Context appContext, Tab tab, @NonNull WebappExtras webappExtras) {
// The pending intents target an activity (instead of a service or a broadcast receiver) so // The pending intents target an activity (instead of a service or a broadcast receiver) so
// that the notification tray closes when a user taps the one of the notification action // that the notification tray closes when a user taps the one of the notification action
// links. // links.
...@@ -83,7 +117,7 @@ class WebappActionsNotificationManager { ...@@ -83,7 +117,7 @@ class WebappActionsNotificationManager {
ChannelDefinitions.ChannelId.WEBAPP_ACTIONS, ChannelDefinitions.ChannelId.WEBAPP_ACTIONS,
null /* remoteAppPackageName */, metadata) null /* remoteAppPackageName */, metadata)
.setSmallIcon(R.drawable.ic_chrome) .setSmallIcon(R.drawable.ic_chrome)
.setContentTitle(webappInfo.shortName()) .setContentTitle(webappExtras.shortName)
.setContentText(appContext.getString(R.string.webapp_tap_to_copy_url)) .setContentText(appContext.getString(R.string.webapp_tap_to_copy_url))
.setShowWhen(false) .setShowWhen(false)
.setAutoCancel(false) .setAutoCancel(false)
......
...@@ -18,7 +18,6 @@ import android.text.TextUtils; ...@@ -18,7 +18,6 @@ import android.text.TextUtils;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import org.chromium.base.ActivityState; import org.chromium.base.ActivityState;
...@@ -87,8 +86,6 @@ public class WebappActivity extends BaseCustomTabActivity<WebappActivityComponen ...@@ -87,8 +86,6 @@ public class WebappActivity extends BaseCustomTabActivity<WebappActivityComponen
private TabObserverRegistrar mTabObserverRegistrar; private TabObserverRegistrar mTabObserverRegistrar;
private CustomTabDelegateFactory mDelegateFactory; private CustomTabDelegateFactory mDelegateFactory;
private WebappDisclosureSnackbarController mDisclosureSnackbarController;
private boolean mIsInitialized; private boolean mIsInitialized;
private Integer mBrandColor; private Integer mBrandColor;
...@@ -140,7 +137,6 @@ public class WebappActivity extends BaseCustomTabActivity<WebappActivityComponen ...@@ -140,7 +137,6 @@ public class WebappActivity extends BaseCustomTabActivity<WebappActivityComponen
*/ */
public WebappActivity() { public WebappActivity() {
mWebappInfo = createWebappInfo(null); mWebappInfo = createWebappInfo(null);
mDisclosureSnackbarController = new WebappDisclosureSnackbarController();
} }
@Override @Override
...@@ -394,36 +390,6 @@ public class WebappActivity extends BaseCustomTabActivity<WebappActivityComponen ...@@ -394,36 +390,6 @@ public class WebappActivity extends BaseCustomTabActivity<WebappActivityComponen
super.onResume(); super.onResume();
} }
@Override
public void onResumeWithNative() {
super.onResumeWithNative();
WebappActionsNotificationManager.maybeShowNotification(getActivityTab(), mWebappInfo);
WebappDataStorage storage =
WebappRegistry.getInstance().getWebappDataStorage(mWebappInfo.id());
if (storage != null) {
mDisclosureSnackbarController.maybeShowDisclosure(this, storage, false /* force */);
}
}
@Override
public void onPauseWithNative() {
WebappActionsNotificationManager.cancelNotification();
super.onPauseWithNative();
}
@Override
protected void initDeferredStartupForActivity() {
super.initDeferredStartupForActivity();
WebappDataStorage storage =
WebappRegistry.getInstance().getWebappDataStorage(mWebappInfo.id());
if (storage != null) {
onDeferredStartupWithStorage(storage);
} else {
onDeferredStartupWithNullStorage(mDisclosureSnackbarController);
}
}
@Override @Override
protected void recordIntentToCreationTime(long timeMs) { protected void recordIntentToCreationTime(long timeMs) {
super.recordIntentToCreationTime(timeMs); super.recordIntentToCreationTime(timeMs);
...@@ -431,15 +397,6 @@ public class WebappActivity extends BaseCustomTabActivity<WebappActivityComponen ...@@ -431,15 +397,6 @@ public class WebappActivity extends BaseCustomTabActivity<WebappActivityComponen
RecordHistogram.recordTimesHistogram("MobileStartup.IntentToCreationTime.WebApp", timeMs); RecordHistogram.recordTimesHistogram("MobileStartup.IntentToCreationTime.WebApp", timeMs);
} }
protected void onDeferredStartupWithStorage(WebappDataStorage storage) {
updateStorage(storage);
}
protected void onDeferredStartupWithNullStorage(
WebappDisclosureSnackbarController disclosureSnackbarController) {
// Overridden in WebApkActivity
}
@Override @Override
public AppMenuPropertiesDelegate createAppMenuPropertiesDelegate() { public AppMenuPropertiesDelegate createAppMenuPropertiesDelegate() {
return new CustomTabAppMenuPropertiesDelegate(this, getActivityTabProvider(), return new CustomTabAppMenuPropertiesDelegate(this, getActivityTabProvider(),
...@@ -479,25 +436,6 @@ public class WebappActivity extends BaseCustomTabActivity<WebappActivityComponen ...@@ -479,25 +436,6 @@ public class WebappActivity extends BaseCustomTabActivity<WebappActivityComponen
return Holder.sWebappInfoMap.remove(id); return Holder.sWebappInfoMap.remove(id);
} }
protected void updateStorage(WebappDataStorage storage) {
// The information in the WebappDataStorage may have been purged by the
// user clearing their history or not launching the web app recently.
// Restore the data if necessary.
storage.updateFromWebappInfo(mWebappInfo);
// A recent last used time is the indicator that the web app is still
// present on the home screen, and enables sources such as notifications to
// launch web apps. Thus, we do not update the last used time when the web
// app is not directly launched from the home screen, as this interferes
// with the heuristic.
if (mWebappInfo.isLaunchedFromHomescreen()) {
// TODO(yusufo): WebappRegistry#unregisterOldWebapps uses this information to delete
// WebappDataStorage objects for legacy webapps which haven't been used in a while.
// That will need to be updated to not delete anything for a TWA which remains installed
storage.updateLastUsedTime();
}
}
protected CustomTabTabObserver createTabObserver() { protected CustomTabTabObserver createTabObserver() {
return new CustomTabTabObserver() { return new CustomTabTabObserver() {
@Override @Override
...@@ -564,14 +502,6 @@ public class WebappActivity extends BaseCustomTabActivity<WebappActivityComponen ...@@ -564,14 +502,6 @@ public class WebappActivity extends BaseCustomTabActivity<WebappActivityComponen
return WebappScopePolicy.Type.LEGACY; return WebappScopePolicy.Type.LEGACY;
} }
/**
* @return The package name if this Activity is associated with an APK. Null if there is no
* associated Android native client.
*/
public @Nullable String getWebApkPackageName() {
return null;
}
@Override @Override
public boolean onMenuOrKeyboardAction(int id, boolean fromMenu) { public boolean onMenuOrKeyboardAction(int id, boolean fromMenu) {
// Disable creating bookmark. // Disable creating bookmark.
......
// 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.chrome.browser.webapps;
import androidx.annotation.NonNull;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.browserservices.BrowserServicesIntentDataProvider;
import org.chromium.chrome.browser.dependency_injection.ActivityScope;
import javax.inject.Inject;
/**
* Coordinator shared between webapp activity and WebAPK activity components.
* Add methods here if other components need to communicate with either of these components.
*/
@ActivityScope
public class WebappActivityCoordinator {
private final WebappActivity mActivity;
private final BrowserServicesIntentDataProvider mIntentDataProvider;
private final WebappDeferredStartupWithStorageHandler mDeferredStartupWithStorageHandler;
@Inject
public WebappActivityCoordinator(ChromeActivity<?> activity,
BrowserServicesIntentDataProvider intentDataProvider,
WebappDeferredStartupWithStorageHandler deferredStartupWithStorageHandler,
WebappActionsNotificationManager actionsNotificationManager) {
// We don't need to do anything with |actionsNotificationManager|. We just need to resolve
// it so that it starts working.
mActivity = (WebappActivity) activity;
mIntentDataProvider = intentDataProvider;
mDeferredStartupWithStorageHandler = deferredStartupWithStorageHandler;
mDeferredStartupWithStorageHandler.addTask((storage, didCreateStorage) -> {
if (activity.isActivityFinishingOrDestroyed()) return;
if (storage != null) {
updateStorage(storage);
}
});
}
/**
* Invoked to add deferred startup tasks to queue.
*/
public void initDeferredStartupForActivity() {
mDeferredStartupWithStorageHandler.initDeferredStartupForActivity();
}
private void updateStorage(@NonNull WebappDataStorage storage) {
// The information in the WebappDataStorage may have been purged by the
// user clearing their history or not launching the web app recently.
// Restore the data if necessary.
WebappInfo webappInfo = mActivity.getWebappInfo();
storage.updateFromWebappInfo(webappInfo);
// A recent last used time is the indicator that the web app is still
// present on the home screen, and enables sources such as notifications to
// launch web apps. Thus, we do not update the last used time when the web
// app is not directly launched from the home screen, as this interferes
// with the heuristic.
if (webappInfo.isLaunchedFromHomescreen()) {
// TODO(yusufo): WebappRegistry#unregisterOldWebapps uses this information to delete
// WebappDataStorage objects for legacy webapps which haven't been used in a while.
storage.updateLastUsedTime();
}
}
}
// 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.chrome.browser.webapps;
import androidx.annotation.Nullable;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.DeferredStartupHandler;
import org.chromium.chrome.browser.browserservices.BrowserServicesIntentDataProvider;
import org.chromium.chrome.browser.dependency_injection.ActivityScope;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
/**
* Requests {@link WebappDataStorage} during deferred startup. For WebAPKs only, creates
* {@link WebappDataStorage} if the WebAPK is not registered. Runs tasks once the
* {@link WebappDataStorage} has been fetched (and perhaps also created).
*/
@ActivityScope
public class WebappDeferredStartupWithStorageHandler {
interface Task {
/**
* Called to run task.
* @param storage Null if there is no {@link WebappDataStorage} registered for the webapp
* and a new entry was not created.
* @param didCreateStorage Whether a new {@link WebappDataStorage} entry was created.
*/
void run(@Nullable WebappDataStorage storage, boolean didCreateStorage);
}
private final ChromeActivity<?> mActivity;
private final @Nullable String mWebApkId;
private final List<Task> mDeferredWithStorageTasks = new ArrayList<>();
@Inject
public WebappDeferredStartupWithStorageHandler(
ChromeActivity<?> activity, BrowserServicesIntentDataProvider intentDataProvider) {
mActivity = activity;
WebappExtras webappExtras = intentDataProvider.getWebappExtras();
mWebApkId = (webappExtras != null && intentDataProvider.isWebApkActivity())
? webappExtras.id
: null;
}
/**
* Invoked to add deferred startup task to queue.
*/
public void initDeferredStartupForActivity() {
DeferredStartupHandler.getInstance().addDeferredTask(() -> { runDeferredTask(); });
}
public void addTask(Task task) {
mDeferredWithStorageTasks.add(task);
}
private void runDeferredTask() {
if (mActivity.isActivityFinishingOrDestroyed()) return;
WebappDataStorage storage = WebappRegistry.getInstance().getWebappDataStorage(mWebApkId);
if (storage != null || mWebApkId == null) {
runTasks(storage, false /* didCreateStorage */);
}
WebappRegistry.getInstance().register(
mWebApkId, new WebappRegistry.FetchWebappDataStorageCallback() {
@Override
public void onWebappDataStorageRetrieved(WebappDataStorage storage) {
runTasks(storage, true /* didCreateStorage */);
}
});
}
public void runTasks(@Nullable WebappDataStorage storage, boolean didCreateStorage) {
for (Task task : mDeferredWithStorageTasks) {
task.run(storage, didCreateStorage);
}
mDeferredWithStorageTasks.clear();
}
}
...@@ -4,11 +4,20 @@ ...@@ -4,11 +4,20 @@
package org.chromium.chrome.browser.webapps; package org.chromium.chrome.browser.webapps;
import androidx.annotation.Nullable;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.browserservices.BrowserServicesIntentDataProvider;
import org.chromium.chrome.browser.dependency_injection.ActivityScope;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.lifecycle.PauseResumeWithNativeObserver;
import org.chromium.chrome.browser.ui.messages.snackbar.Snackbar; import org.chromium.chrome.browser.ui.messages.snackbar.Snackbar;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager; import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
import org.chromium.webapk.lib.common.WebApkConstants; import org.chromium.webapk.lib.common.WebApkConstants;
import javax.inject.Inject;
/** /**
* Unbound WebAPKs are part of Chrome. They have access to cookies and report metrics the same way * Unbound WebAPKs are part of Chrome. They have access to cookies and report metrics the same way
* as the rest of Chrome. However, there is no UI indicating they are running in Chrome. For privacy * as the rest of Chrome. However, there is no UI indicating they are running in Chrome. For privacy
...@@ -18,7 +27,53 @@ import org.chromium.webapk.lib.common.WebApkConstants; ...@@ -18,7 +27,53 @@ import org.chromium.webapk.lib.common.WebApkConstants;
* as long as the app is open. It should remain active even across pause/resume and should show the * as long as the app is open. It should remain active even across pause/resume and should show the
* next time the app is opened if it hasn't been acknowledged. * next time the app is opened if it hasn't been acknowledged.
*/ */
public class WebappDisclosureSnackbarController implements SnackbarManager.SnackbarController { @ActivityScope
public class WebappDisclosureSnackbarController
implements SnackbarManager.SnackbarController, PauseResumeWithNativeObserver {
private final ChromeActivity mActivity;
private final BrowserServicesIntentDataProvider mIntentDataProvider;
@Inject
public WebappDisclosureSnackbarController(ChromeActivity<?> activity,
BrowserServicesIntentDataProvider intentDataProvider,
WebappDeferredStartupWithStorageHandler deferredStartupWithStorageHandler,
ActivityLifecycleDispatcher lifecycleDispatcher) {
mActivity = activity;
mIntentDataProvider = intentDataProvider;
lifecycleDispatcher.register(this);
deferredStartupWithStorageHandler.addTask((storage, didCreateStorage) -> {
if (activity.isActivityFinishingOrDestroyed()) return;
onDeferredStartupWithStorage(storage, didCreateStorage);
});
}
public void onDeferredStartupWithStorage(
@Nullable WebappDataStorage storage, boolean didCreateStorage) {
if (didCreateStorage) {
// Set force == true to indicate that we need to show a privacy disclosure for the newly
// installed unbound WebAPKs which have no storage yet. We can't simply default to a
// showing if the storage has a default value as we don't want to show this disclosure
// for pre-existing unbound WebAPKs.
maybeShowDisclosure(storage, true /* force */);
}
}
@Override
public void onResumeWithNative() {
WebappExtras webappExtras = mIntentDataProvider.getWebappExtras();
WebappDataStorage storage = WebappRegistry.getInstance().getWebappDataStorage(
mIntentDataProvider.getWebappExtras().id);
if (storage != null) {
maybeShowDisclosure(storage, false /* force */);
}
}
@Override
public void onPauseWithNative() {}
/** /**
* @param actionData an instance of WebappInfo * @param actionData an instance of WebappInfo
*/ */
...@@ -37,45 +92,41 @@ public class WebappDisclosureSnackbarController implements SnackbarManager.Snack ...@@ -37,45 +92,41 @@ public class WebappDisclosureSnackbarController implements SnackbarManager.Snack
/** /**
* Shows the disclosure informing the user the Webapp is running in Chrome. * Shows the disclosure informing the user the Webapp is running in Chrome.
* @param activity Webapp activity to show disclosure for.
* @param storage Storage for the Webapp. * @param storage Storage for the Webapp.
* @param force Whether to force showing the Snackbar (if no storage is available on start). * @param force Whether to force showing the Snackbar (if no storage is available on start).
*/ */
public void maybeShowDisclosure( private void maybeShowDisclosure(WebappDataStorage storage, boolean force) {
WebappActivity activity, WebappDataStorage storage, boolean force) {
if (storage == null) return; if (storage == null) return;
// If forced we set the bit to show the disclosure. This persists to future instances. // If forced we set the bit to show the disclosure. This persists to future instances.
if (force) storage.setShowDisclosure(); if (force) storage.setShowDisclosure();
if (shouldShowDisclosure(activity, storage)) { if (shouldShowDisclosure(storage)) {
activity.getSnackbarManager().showSnackbar( mActivity.getSnackbarManager().showSnackbar(
Snackbar.make(activity.getResources().getString( Snackbar.make(mActivity.getResources().getString(
R.string.app_running_in_chrome_disclosure), R.string.app_running_in_chrome_disclosure),
this, Snackbar.TYPE_PERSISTENT, this, Snackbar.TYPE_PERSISTENT,
Snackbar.UMA_WEBAPK_PRIVACY_DISCLOSURE) Snackbar.UMA_WEBAPK_PRIVACY_DISCLOSURE)
.setAction( .setAction(mActivity.getResources().getString(R.string.ok), storage)
activity.getResources().getString(R.string.ok), storage)
.setSingleLine(false)); .setSingleLine(false));
} }
} }
/** /**
* Restricts showing to TWAs and unbound WebAPKs that haven't dismissed the disclosure. * Restricts showing to unbound WebAPKs that haven't dismissed the disclosure.
* @param activity Webapp activity.
* @param storage Storage for the Webapp. * @param storage Storage for the Webapp.
* @return boolean indicating whether to show the privacy disclosure. * @return boolean indicating whether to show the privacy disclosure.
*/ */
private boolean shouldShowDisclosure(WebappActivity activity, WebappDataStorage storage) { private boolean shouldShowDisclosure(WebappDataStorage storage) {
// Show only if the correct flag is set. // Show only if the correct flag is set.
if (!storage.shouldShowDisclosure()) { if (!storage.shouldShowDisclosure()) {
return false; return false;
} }
// This will be null for Webapps or bound WebAPKs.
String packageName = activity.getWebApkPackageName();
// Show for unbound WebAPKs. // Show for unbound WebAPKs.
return packageName != null WebApkExtras webApkExtras = mIntentDataProvider.getWebApkExtras();
&& !packageName.startsWith(WebApkConstants.WEBAPK_PACKAGE_PREFIX); return webApkExtras != null && webApkExtras.webApkPackageName != null
&& !webApkExtras.webApkPackageName.startsWith(
WebApkConstants.WEBAPK_PACKAGE_PREFIX);
} }
} }
...@@ -67,6 +67,11 @@ public class WebappIntentDataProvider extends BrowserServicesIntentDataProvider ...@@ -67,6 +67,11 @@ public class WebappIntentDataProvider extends BrowserServicesIntentDataProvider
return true; return true;
} }
@Override
public boolean isWebApkActivity() {
return mWebApkExtras != null;
}
@Override @Override
@Nullable @Nullable
public WebappExtras getWebappExtras() { public WebappExtras getWebappExtras() {
......
// 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.chrome.browser.webapps;
import static org.junit.Assert.assertTrue;
import android.support.test.filters.LargeTest;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.dependency_injection.ChromeActivityCommonsModule;
import org.chromium.chrome.browser.dependency_injection.ModuleOverridesRule;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.lifecycle.LifecycleObserver;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.util.browser.webapps.WebApkInfoBuilder;
import org.chromium.net.test.EmbeddedTestServer;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeoutException;
/**
* Tests that the expected classes are constructed when a {@link WebApkActivity} is launched.
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class WebApkInitializationTest {
/**
* {@link ActivityLifecycleDispatcher} wrapper which tracks {@link LifecycleObserver}
* registrations.
*/
private static class TrackingActivityLifecycleDispatcher
implements ActivityLifecycleDispatcher {
private ActivityLifecycleDispatcher mRealActivityLifecycleDispatcher;
private Set<String> mRegisteredObserverClassNames = new HashSet<>();
public void init(ActivityLifecycleDispatcher realActivityLifecycleDispatcher) {
mRealActivityLifecycleDispatcher = realActivityLifecycleDispatcher;
}
/**
* Returns set of all the {@link LifecycleObserver} subclasses which have registered with
* the {@link ActivityLifecycleDispatcher}.
*/
public Set<String> getRegisteredObserverClassNames() {
return mRegisteredObserverClassNames;
}
@Override
public void register(LifecycleObserver observer) {
mRegisteredObserverClassNames.add(observer.getClass().getName());
mRealActivityLifecycleDispatcher.register(observer);
}
@Override
public void unregister(LifecycleObserver observer) {
mRealActivityLifecycleDispatcher.unregister(observer);
}
@Override
public @ActivityState int getCurrentActivityState() {
return mRealActivityLifecycleDispatcher.getCurrentActivityState();
}
@Override
public boolean isNativeInitializationFinished() {
return true;
}
}
private final TrackingActivityLifecycleDispatcher mTrackingActivityLifecycleDispatcher =
new TrackingActivityLifecycleDispatcher();
private final TestRule mModuleOverridesRule = new ModuleOverridesRule().setOverride(
ChromeActivityCommonsModule.Factory.class, (activity, lifecycleDispatcher) -> {
mTrackingActivityLifecycleDispatcher.init(lifecycleDispatcher);
return new ChromeActivityCommonsModule(
activity, mTrackingActivityLifecycleDispatcher);
});
private final WebApkActivityTestRule mActivityRule = new WebApkActivityTestRule();
@Rule
public final TestRule mRuleChain =
RuleChain.outerRule(mModuleOverridesRule).around(mActivityRule);
/**
* Test that {@link WebappActionsNotificationManager} and
* {@link WebappDisclosureSnackbarController} are constructed when a {@link WebApkActivity} is
* launched.
*/
@Test
@LargeTest
@Feature({"WebApk"})
public void testInitialization() throws TimeoutException {
EmbeddedTestServer embeddedTestServer =
mActivityRule.getEmbeddedTestServerRule().getServer();
WebApkInfoBuilder webApkInfoBuilder = new WebApkInfoBuilder(
"org.chromium.webapk.for.testing",
embeddedTestServer.getURL("/chrome/test/data/banners/manifest_test_page.html"));
mActivityRule.startWebApkActivity(webApkInfoBuilder.build());
Set<String> registeredObserverClassNames =
mTrackingActivityLifecycleDispatcher.getRegisteredObserverClassNames();
assertTrue(registeredObserverClassNames.contains(
WebappActionsNotificationManager.class.getName()));
assertTrue(registeredObserverClassNames.contains(
WebappDisclosureSnackbarController.class.getName()));
}
}
...@@ -9,6 +9,7 @@ import static org.junit.Assert.assertTrue; ...@@ -9,6 +9,7 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
...@@ -19,12 +20,17 @@ import org.junit.Test; ...@@ -19,12 +20,17 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.robolectric.android.util.concurrent.RoboExecutorService;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import org.chromium.base.task.PostTask;
import org.chromium.base.test.BaseRobolectricTestRunner; import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Feature; import org.chromium.base.test.util.Feature;
import org.chromium.chrome.browser.browserservices.BrowserServicesIntentDataProvider;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.ui.messages.snackbar.Snackbar; import org.chromium.chrome.browser.ui.messages.snackbar.Snackbar;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager; import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
import org.chromium.chrome.test.util.browser.webapps.WebApkInfoBuilder;
import org.chromium.webapk.lib.common.WebApkConstants; import org.chromium.webapk.lib.common.WebApkConstants;
/** /**
...@@ -43,23 +49,41 @@ public class WebappDisclosureSnackbarControllerTest { ...@@ -43,23 +49,41 @@ public class WebappDisclosureSnackbarControllerTest {
@Before @Before
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
// Run AsyncTasks synchronously.
PostTask.setPrenativeThreadPoolExecutorForTesting(new RoboExecutorService());
doReturn("test text").when(mResources).getString(anyInt()); doReturn("test text").when(mResources).getString(anyInt());
doReturn(mManager).when(mActivity).getSnackbarManager(); doReturn(mManager).when(mActivity).getSnackbarManager();
doReturn(mResources).when(mActivity).getResources(); doReturn(mResources).when(mActivity).getResources();
} }
private WebappDisclosureSnackbarController buildControllerForWebApk(String webApkPackageName) {
BrowserServicesIntentDataProvider intentDataProvider =
new WebApkInfoBuilder(webApkPackageName, "https://pwa.rocks/")
.build()
.getProvider();
return new WebappDisclosureSnackbarController(mActivity, intentDataProvider,
mock(WebappDeferredStartupWithStorageHandler.class),
mock(ActivityLifecycleDispatcher.class));
}
private WebappDataStorage registerStorageForWebApk(String packageName) {
String id = WebappRegistry.webApkIdForPackage(packageName);
WebappRegistry.getInstance().register(id, (storage) -> {});
return WebappRegistry.getInstance().getWebappDataStorage(id);
}
public void verifyShownThenDismissedOnNewCreateStorage(String packageName) { public void verifyShownThenDismissedOnNewCreateStorage(String packageName) {
WebappDisclosureSnackbarController controller = new WebappDisclosureSnackbarController(); WebappDisclosureSnackbarController controller = buildControllerForWebApk(packageName);
WebappDataStorage storage = WebappDataStorage.open(packageName); WebappDataStorage storage = registerStorageForWebApk(packageName);
// Simulates the case that shows the disclosure when creating a new storage. // Simulates the case that shows the disclosure when creating a new storage.
controller.maybeShowDisclosure(mActivity, storage, true); controller.onDeferredStartupWithStorage(storage, true /* didCreateStorage */);
verify(mManager, times(1)).showSnackbar(any(Snackbar.class)); verify(mManager, times(1)).showSnackbar(any(Snackbar.class));
assertTrue(storage.shouldShowDisclosure()); assertTrue(storage.shouldShowDisclosure());
// Simulate a restart or a resume (has storage so `force = false`). // Simulate a restart or a resume.
controller.maybeShowDisclosure(mActivity, storage, false); controller.onResumeWithNative();
verify(mManager, times(2)).showSnackbar(any(Snackbar.class)); verify(mManager, times(2)).showSnackbar(any(Snackbar.class));
assertTrue(storage.shouldShowDisclosure()); assertTrue(storage.shouldShowDisclosure());
...@@ -68,36 +92,34 @@ public class WebappDisclosureSnackbarControllerTest { ...@@ -68,36 +92,34 @@ public class WebappDisclosureSnackbarControllerTest {
// Simulate resuming or starting again this time no disclosure should show. // Simulate resuming or starting again this time no disclosure should show.
assertFalse(storage.shouldShowDisclosure()); assertFalse(storage.shouldShowDisclosure());
controller.maybeShowDisclosure(mActivity, storage, false); controller.onResumeWithNative();
verify(mManager, times(2)).showSnackbar(any(Snackbar.class)); verify(mManager, times(2)).showSnackbar(any(Snackbar.class));
storage.delete(); storage.delete();
} }
public void verifyNotShownOnExistingStorageWithoutShouldShowDisclosure(String packageName) { public void verifyNotShownOnExistingStorageWithoutShouldShowDisclosure(String packageName) {
WebappDisclosureSnackbarController controller = new WebappDisclosureSnackbarController(); WebappDisclosureSnackbarController controller = buildControllerForWebApk(packageName);
WebappDataStorage storage = WebappDataStorage.open(packageName); WebappDataStorage storage = registerStorageForWebApk(packageName);
// Simulate that starting with existing storage will not cause the disclosure to show. // Simulate that starting with existing storage will not cause the disclosure to show.
assertFalse(storage.shouldShowDisclosure()); assertFalse(storage.shouldShowDisclosure());
controller.maybeShowDisclosure(mActivity, storage, false); controller.onDeferredStartupWithStorage(storage, false /* didCreateStorage */);
verify(mManager, times(0)).showSnackbar(any(Snackbar.class)); verify(mManager, times(0)).showSnackbar(any(Snackbar.class));
storage.delete(); storage.delete();
} }
public void verifyNeverShown(String packageName) { public void verifyNeverShown(String packageName) {
WebappDisclosureSnackbarController controller = new WebappDisclosureSnackbarController(); WebappDisclosureSnackbarController controller = buildControllerForWebApk(packageName);
WebappDataStorage storage = WebappDataStorage.open(packageName); WebappDataStorage storage = registerStorageForWebApk(packageName);
// Try to show the disclosure the first time (fake having no storage on startup by setting // Try to show the disclosure the first time.
// `force = true`) this shouldn't work as the app was installed via Chrome. controller.onDeferredStartupWithStorage(storage, true /* didCreateStorage */);
controller.maybeShowDisclosure(mActivity, storage, true);
verify(mManager, times(0)).showSnackbar(any(Snackbar.class)); verify(mManager, times(0)).showSnackbar(any(Snackbar.class));
// Try to the disclosure again this time emulating a restart or a resume (fake having // Try to the disclosure again this time emulating a restart or a resume.
// storage `force = false`) again this shouldn't work. controller.onResumeWithNative();
controller.maybeShowDisclosure(mActivity, storage, false);
verify(mManager, times(0)).showSnackbar(any(Snackbar.class)); verify(mManager, times(0)).showSnackbar(any(Snackbar.class));
storage.delete(); storage.delete();
...@@ -107,8 +129,6 @@ public class WebappDisclosureSnackbarControllerTest { ...@@ -107,8 +129,6 @@ public class WebappDisclosureSnackbarControllerTest {
@Feature({"Webapps"}) @Feature({"Webapps"})
public void testUnboundWebApkShowDisclosure() { public void testUnboundWebApkShowDisclosure() {
String packageName = "unbound"; String packageName = "unbound";
doReturn(packageName).when(mActivity).getWebApkPackageName();
verifyShownThenDismissedOnNewCreateStorage(packageName); verifyShownThenDismissedOnNewCreateStorage(packageName);
} }
...@@ -122,17 +142,6 @@ public class WebappDisclosureSnackbarControllerTest { ...@@ -122,17 +142,6 @@ public class WebappDisclosureSnackbarControllerTest {
@Feature({"Webapps"}) @Feature({"Webapps"})
public void testBoundWebApkNoDisclosure() { public void testBoundWebApkNoDisclosure() {
String packageName = WebApkConstants.WEBAPK_PACKAGE_PREFIX + ".bound"; String packageName = WebApkConstants.WEBAPK_PACKAGE_PREFIX + ".bound";
doReturn(packageName).when(mActivity).getWebApkPackageName();
verifyNeverShown(packageName);
}
@Test
@Feature({"Webapps"})
public void testWebappNoDisclosure() {
String packageName = "webapp";
// Don't set a client package name, it should be null for Webapps.
verifyNeverShown(packageName); verifyNeverShown(packageName);
} }
} }
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