Commit 3a5daa30 authored by Pavel Shmakov's avatar Pavel Shmakov Committed by Commit Bot

Refactor Trusted Web Activity UI code to MVC

The code is restructured according to the high-level MVC picture, but
low-level details such as view binding mechanism are not used, as
it seems to be an overkill at this point. We can easily install those
mechanisms in future should the model become more complicated.

Verification is extracted to TrustedWebActivityVerifier in the
controller layer. Several controllers observe verification and update
the model. Several independent Views are observing the changes in the
model and updating themselves accordingly.

Change-Id: I8882da2d648fcea21b85fd4a1c0568dd00e47113
Reviewed-on: https://chromium-review.googlesource.com/c/1323069Reviewed-by: default avatarPeter Conn <peconn@chromium.org>
Commit-Queue: Pavel Shmakov <pshmakov@chromium.org>
Cr-Commit-Position: refs/heads/master@{#606853}
parent e57afbcb
...@@ -42,7 +42,7 @@ public class ClientAppDataRegister { ...@@ -42,7 +42,7 @@ public class ClientAppDataRegister {
* is removed or cleared, we should consider doing the same with Chrome data relevant to * is removed or cleared, we should consider doing the same with Chrome data relevant to
* |domain|. * |domain|.
*/ */
/* package */ void registerPackageForDomain(int uid, String appName, String domain) { public void registerPackageForDomain(int uid, String appName, String domain) {
// Store the UID in the main Chrome Preferences. // Store the UID in the main Chrome Preferences.
Set<String> uids = getUids(); Set<String> uids = getUids();
uids.add(String.valueOf(uid)); uids.add(String.valueOf(uid));
...@@ -66,7 +66,7 @@ public class ClientAppDataRegister { ...@@ -66,7 +66,7 @@ public class ClientAppDataRegister {
return new HashSet<>(mPreferences.getStringSet(UIDS_KEY, Collections.emptySet())); return new HashSet<>(mPreferences.getStringSet(UIDS_KEY, Collections.emptySet()));
} }
/* package */ void removePackage(int uid) { public void removePackage(int uid) {
Set<String> uids = getUids(); Set<String> uids = getUids();
uids.remove(String.valueOf(uid)); uids.remove(String.valueOf(uid));
setUids(uids); setUids(uids);
......
// Copyright 2018 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.browserservices.trustedwebactivityui;
import org.chromium.chrome.browser.browserservices.trustedwebactivityui.controller.PersistentNotificationController;
import org.chromium.chrome.browser.browserservices.trustedwebactivityui.controller.TrustedWebActivityDisclosureController;
import org.chromium.chrome.browser.browserservices.trustedwebactivityui.controller.TrustedWebActivityOpenTimeRecorder;
import org.chromium.chrome.browser.browserservices.trustedwebactivityui.controller.TrustedWebActivityToolbarController;
import org.chromium.chrome.browser.browserservices.trustedwebactivityui.view.PersistentNotificationView;
import org.chromium.chrome.browser.browserservices.trustedwebactivityui.view.TrustedWebActivityDisclosureView;
import org.chromium.chrome.browser.browserservices.trustedwebactivityui.view.TrustedWebActivityToolbarView;
import org.chromium.chrome.browser.dependency_injection.ActivityScope;
import javax.inject.Inject;
/**
* Coordinator for the Trusted Web Activity component.
* Add methods here if other components need to communicate with Trusted Web Activity component.
*/
@ActivityScope
public class TrustedWebActivityCoordinator {
@Inject
public TrustedWebActivityCoordinator(
PersistentNotificationController persistentNotificationController,
TrustedWebActivityDisclosureController disclosureController,
TrustedWebActivityToolbarController toolbarController,
TrustedWebActivityToolbarView toolbarView,
TrustedWebActivityDisclosureView disclosureView,
PersistentNotificationView notificationView,
TrustedWebActivityOpenTimeRecorder openTimeRecorder) {
// Do nothing for now, just resolve the classes that need to start working.
}
}
// Copyright 2018 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.browserservices.trustedwebactivityui;
import android.content.Intent;
import org.chromium.chrome.browser.browserservices.Origin;
import org.chromium.chrome.browser.dependency_injection.ActivityScope;
import org.chromium.chrome.browser.modelutil.PropertyModel;
import javax.inject.Inject;
/**
* Model describing the state of a Trusted Web Activity.
*/
@ActivityScope
public class TrustedWebActivityModel extends PropertyModel {
/** Whether toolbar should be hidden. */
public static final WritableBooleanPropertyKey TOOLBAR_HIDDEN =
new WritableBooleanPropertyKey();
/** The state of Trusted Web Activity disclosure. Can be one of the constants below. */
public static final WritableIntPropertyKey DISCLOSURE_STATE =
new WritableIntPropertyKey();
public static final int DISCLOSURE_STATE_NOT_SHOWN = 0;
public static final int DISCLOSURE_STATE_SHOWN = 1;
public static final int DISCLOSURE_STATE_DISMISSED_BY_USER = 2;
/** Tag to use for showing and dismissing a persistent notification. */
public static final WritableObjectPropertyKey<String>
PERSISTENT_NOTIFICATION_TAG = new WritableObjectPropertyKey<>();
/**
* Data for building a persistent notification when it needs to be shown.
* Null when it needs to be hidden.
*/
public static final WritableObjectPropertyKey<PersistentNotificationData>
PERSISTENT_NOTIFICATION = new WritableObjectPropertyKey<>();
/** Callback for routing disclosure-related view events back to controller side. */
public static final WritableObjectPropertyKey<DisclosureEventsCallback>
DISCLOSURE_EVENTS_CALLBACK = new WritableObjectPropertyKey<>();
public static class PersistentNotificationData {
// Necessary for making a PendingIntent for sharing.
public final Intent customTabActivityIntent;
public final Origin origin;
public PersistentNotificationData(Intent customTabActivityIntent, Origin origin) {
this.customTabActivityIntent = customTabActivityIntent;
this.origin = origin;
}
}
public interface DisclosureEventsCallback {
/** Called when user accepted the disclosure. */
void onDisclosureAccepted();
}
@Inject
public TrustedWebActivityModel() {
super(TOOLBAR_HIDDEN, DISCLOSURE_STATE, PERSISTENT_NOTIFICATION,
PERSISTENT_NOTIFICATION_TAG, DISCLOSURE_EVENTS_CALLBACK);
}
}
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
package org.chromium.chrome.browser.browserservices; package org.chromium.chrome.browser.browserservices.trustedwebactivityui.controller;
import static org.chromium.chrome.browser.dependency_injection.ChromeCommonQualifiers.APP_CONTEXT; import static org.chromium.chrome.browser.dependency_injection.ChromeCommonQualifiers.APP_CONTEXT;
...@@ -12,6 +12,8 @@ import android.content.pm.PackageManager; ...@@ -12,6 +12,8 @@ import android.content.pm.PackageManager;
import android.text.TextUtils; import android.text.TextUtils;
import org.chromium.base.Log; import org.chromium.base.Log;
import org.chromium.chrome.browser.browserservices.ClientAppDataRegister;
import org.chromium.chrome.browser.browserservices.Origin;
import org.chromium.chrome.browser.dependency_injection.ActivityScope; import org.chromium.chrome.browser.dependency_injection.ActivityScope;
import org.chromium.chrome.browser.util.UrlUtilities; import org.chromium.chrome.browser.util.UrlUtilities;
...@@ -29,8 +31,8 @@ import javax.inject.Named; ...@@ -29,8 +31,8 @@ import javax.inject.Named;
* - Transforming the origin into a domain (requires native). * - Transforming the origin into a domain (requires native).
* *
* Lifecycle: There should be a 1-1 relationship between this class and * Lifecycle: There should be a 1-1 relationship between this class and
* {@link TrustedWebActivityUi}. Having more instances won't effect correctness, but will limit the * {@link TrustedWebActivityVerifier}. Having more instances won't effect correctness, but will
* performance benefits of the cache. * limit the performance benefits of the cache.
* Thread safety: All methods on this class should be called from the same thread. * Thread safety: All methods on this class should be called from the same thread.
*/ */
@ActivityScope @ActivityScope
...@@ -49,7 +51,7 @@ public class ClientAppDataRecorder { ...@@ -49,7 +51,7 @@ public class ClientAppDataRecorder {
private final Set<String> mCache = new HashSet<>(); private final Set<String> mCache = new HashSet<>();
@Inject @Inject
public ClientAppDataRecorder(@Named(APP_CONTEXT) Context context, ClientAppDataRecorder(@Named(APP_CONTEXT) Context context,
ClientAppDataRegister clientAppDataRegister) { ClientAppDataRegister clientAppDataRegister) {
mPackageManager = context.getPackageManager(); mPackageManager = context.getPackageManager();
mClientAppDataRegister = clientAppDataRegister; mClientAppDataRegister = clientAppDataRegister;
...@@ -64,7 +66,7 @@ public class ClientAppDataRecorder { ...@@ -64,7 +66,7 @@ public class ClientAppDataRecorder {
/* package */ void register(String packageName, Origin origin) { /* package */ void register(String packageName, Origin origin) {
if (mCache.contains(combine(packageName, origin))) return; if (mCache.contains(combine(packageName, origin))) return;
mCache.add(combine(packageName, origin)); mCache.add(combine(packageName, origin));
;
try { try {
ApplicationInfo ai = mPackageManager.getApplicationInfo(packageName, 0); ApplicationInfo ai = mPackageManager.getApplicationInfo(packageName, 0);
String appLabel = mPackageManager.getApplicationLabel(ai).toString(); String appLabel = mPackageManager.getApplicationLabel(ai).toString();
......
// Copyright 2018 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.browserservices.trustedwebactivityui.controller;
import static org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel.PERSISTENT_NOTIFICATION;
import static org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel.PERSISTENT_NOTIFICATION_TAG;
import android.os.Handler;
import android.support.annotation.Nullable;
import org.chromium.chrome.browser.browserservices.Origin;
import org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel;
import org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel.PersistentNotificationData;
import org.chromium.chrome.browser.browserservices.trustedwebactivityui.controller.TrustedWebActivityVerifier.VerificationState;
import org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider;
import org.chromium.chrome.browser.dependency_injection.ActivityScope;
import org.chromium.chrome.browser.init.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.lifecycle.StartStopWithNativeObserver;
import javax.inject.Inject;
/**
* Controls when the TWA persistent notification should be shown or hidden.
* The notification is shown while the activity is in started state. It is not removed when the user
* leaves the origin associated with the app by following links.
*/
@ActivityScope
public class PersistentNotificationController implements StartStopWithNativeObserver {
private final CustomTabIntentDataProvider mIntentDataProvider;
private final TrustedWebActivityModel mModel;
private final TrustedWebActivityVerifier mVerifier;
private boolean mStarted;
// Origin for which the notification is currently shown. Null when notification not shown.
@Nullable
private Origin mLastVerifiedOrigin;
@Nullable
private Handler mHandler;
@Inject
public PersistentNotificationController(ActivityLifecycleDispatcher lifecycleDispatcher,
CustomTabIntentDataProvider intentDataProvider,
TrustedWebActivityModel model,
TrustedWebActivityVerifier verifier) {
mIntentDataProvider = intentDataProvider;
mModel = model;
mVerifier = verifier;
lifecycleDispatcher.register(this);
mVerifier.addVerificationObserver(this::onVerificationStateChanged);
mModel.set(PERSISTENT_NOTIFICATION_TAG, mVerifier.getClientPackageName());
}
private void onVerificationStateChanged() {
VerificationState state = mVerifier.getState();
if (state == null) {
return;
}
if (state.status == TrustedWebActivityVerifier.VERIFICATION_FAILURE) {
return; // Keep showing the notification despite we've left the verified origin
}
if (state.origin.equals(mLastVerifiedOrigin)) {
return;
}
mLastVerifiedOrigin = state.origin;
if (mStarted) {
show();
}
}
@Override
public void onStartWithNative() {
mStarted = true;
if (mLastVerifiedOrigin != null) {
show();
}
}
@Override
public void onStopWithNative() {
mStarted = false;
hide();
}
private void show() {
assert mLastVerifiedOrigin != null;
mModel.set(PERSISTENT_NOTIFICATION, new PersistentNotificationData(
mIntentDataProvider.getIntent(), mLastVerifiedOrigin));
}
private void hide() {
mModel.set(PERSISTENT_NOTIFICATION, null);
}
}
// Copyright 2018 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.browserservices.trustedwebactivityui.controller;
import static org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel.DISCLOSURE_EVENTS_CALLBACK;
import static org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel.DISCLOSURE_STATE;
import static org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel.DISCLOSURE_STATE_DISMISSED_BY_USER;
import static org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel.DISCLOSURE_STATE_NOT_SHOWN;
import static org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel.DISCLOSURE_STATE_SHOWN;
import static org.chromium.chrome.browser.browserservices.trustedwebactivityui.controller.TrustedWebActivityVerifier.VERIFICATION_FAILURE;
import org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel;
import org.chromium.chrome.browser.browserservices.trustedwebactivityui.controller.TrustedWebActivityVerifier.VerificationState;
import org.chromium.chrome.browser.init.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.lifecycle.NativeInitObserver;
import org.chromium.chrome.browser.preferences.ChromePreferenceManager;
import javax.inject.Inject;
/**
* Controls when Trusted Web Activity disclosure should be shown and hidden, reacts to interaction
* with it.
*/
public class TrustedWebActivityDisclosureController implements NativeInitObserver,
TrustedWebActivityModel.DisclosureEventsCallback {
private final ChromePreferenceManager mPreferenceManager;
private final TrustedWebActivityModel mModel;
private final TrustedWebActivityVerifier mVerifier;
@Inject
TrustedWebActivityDisclosureController(
ChromePreferenceManager preferenceManager,
TrustedWebActivityModel model,
ActivityLifecycleDispatcher lifecycleDispatcher,
TrustedWebActivityVerifier verifier) {
mVerifier = verifier;
mPreferenceManager = preferenceManager;
mModel = model;
model.set(DISCLOSURE_EVENTS_CALLBACK, this);
verifier.addVerificationObserver(this::onVerificationStatusChanged);
lifecycleDispatcher.register(this);
}
private void onVerificationStatusChanged() {
if (shouldShowInCurrentState()) {
showIfNeeded();
} else {
dismiss();
}
}
@Override
public void onDisclosureAccepted() {
mPreferenceManager.setUserAcceptedTwaDisclosureForPackage(mVerifier.getClientPackageName());
mModel.set(DISCLOSURE_STATE, DISCLOSURE_STATE_DISMISSED_BY_USER);
}
/** Shows the disclosure if it is not already showing and hasn't been accepted. */
private void showIfNeeded() {
if (!isShowing() && !wasDismissed()) {
mModel.set(DISCLOSURE_STATE, DISCLOSURE_STATE_SHOWN);
}
}
/** Dismisses the disclosure if it is showing. */
private void dismiss() {
if (isShowing()) {
mModel.set(DISCLOSURE_STATE, DISCLOSURE_STATE_NOT_SHOWN);
}
}
/** Has a disclosure been dismissed for this client package before? */
private boolean wasDismissed() {
return mPreferenceManager.hasUserAcceptedTwaDisclosureForPackage(
mVerifier.getClientPackageName());
}
@Override
public void onFinishNativeInitialization() {
// We want to show disclosure ASAP, which is limited by SnackbarManager requiring native.
if (shouldShowInCurrentState()) {
showIfNeeded();
}
}
private boolean shouldShowInCurrentState() {
VerificationState state = mVerifier.getState();
return state != null && state.status != VERIFICATION_FAILURE;
}
public boolean isShowing() {
return mModel.get(DISCLOSURE_STATE) == DISCLOSURE_STATE_SHOWN;
}
}
...@@ -2,29 +2,38 @@ ...@@ -2,29 +2,38 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
package org.chromium.chrome.browser.browserservices; package org.chromium.chrome.browser.browserservices.trustedwebactivityui.controller;
import android.os.SystemClock; import android.os.SystemClock;
import org.chromium.chrome.browser.browserservices.BrowserServicesMetrics;
import org.chromium.chrome.browser.dependency_injection.ActivityScope;
import org.chromium.chrome.browser.init.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.lifecycle.PauseResumeWithNativeObserver;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
/** /**
* Records how long Trusted Web Activities are used for. * Records how long Trusted Web Activities are used for.
*
* Lifecycle: There should be a 1-1 relationship between this class and
* {@link TrustedWebActivityUi} (and transitively {@link CustomTabActivity}).
* Thread safety: All methods on this class should be called on the UI thread.
*/ */
class TrustedWebActivityOpenTimeRecorder { @ActivityScope
public class TrustedWebActivityOpenTimeRecorder implements PauseResumeWithNativeObserver {
private long mOnResumeTimestampMs; private long mOnResumeTimestampMs;
/** Notify that the TWA has been resumed. */ @Inject
public void onResume() { TrustedWebActivityOpenTimeRecorder(ActivityLifecycleDispatcher lifecycleDispatcher) {
lifecycleDispatcher.register(this);
}
@Override
public void onResumeWithNative() {
mOnResumeTimestampMs = SystemClock.elapsedRealtime(); mOnResumeTimestampMs = SystemClock.elapsedRealtime();
} }
/** Notify that the TWA has been paused. */ @Override
public void onPause() { public void onPauseWithNative() {
assert mOnResumeTimestampMs != 0; assert mOnResumeTimestampMs != 0;
BrowserServicesMetrics.recordTwaOpenTime( BrowserServicesMetrics.recordTwaOpenTime(
SystemClock.elapsedRealtime() - mOnResumeTimestampMs, TimeUnit.MILLISECONDS); SystemClock.elapsedRealtime() - mOnResumeTimestampMs, TimeUnit.MILLISECONDS);
......
// Copyright 2018 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.browserservices.trustedwebactivityui.controller;
import static org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel.TOOLBAR_HIDDEN;
import static org.chromium.chrome.browser.browserservices.trustedwebactivityui.controller.TrustedWebActivityVerifier.VERIFICATION_FAILURE;
import org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel;
import org.chromium.chrome.browser.browserservices.trustedwebactivityui.controller.TrustedWebActivityVerifier.VerificationState;
import org.chromium.chrome.browser.dependency_injection.ActivityScope;
import org.chromium.chrome.browser.init.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.lifecycle.InflationObserver;
import javax.inject.Inject;
/**
* Controls the visibility of the toolbar in Trusted Web Activity.
* Toolbar is hidden when user browses the pages belonging to the verified origin and appears when
* user leaves the verified origin.
*/
@ActivityScope
public class TrustedWebActivityToolbarController implements InflationObserver {
private final TrustedWebActivityModel mModel;
private final TrustedWebActivityVerifier mVerifier;
@Inject
public TrustedWebActivityToolbarController(
TrustedWebActivityModel model,
TrustedWebActivityVerifier verifier,
ActivityLifecycleDispatcher lifecycleDispatcher) {
mModel = model;
mVerifier = verifier;
mVerifier.addVerificationObserver(this::handleVerificationUpdate);
lifecycleDispatcher.register(this);
}
@Override
public void onPreInflationStartup() {}
@Override
public void onPostInflationStartup() {
// Before the verification completes, we optimistically expect it to be successful and apply
// the trusted web activity mode to UI. So hide the toolbar as soon as possible.
if (mVerifier.getState() == null) {
mModel.set(TOOLBAR_HIDDEN, true);
}
}
private void handleVerificationUpdate() {
VerificationState state = mVerifier.getState();
boolean shouldHide = state == null || state.status != VERIFICATION_FAILURE;
mModel.set(TOOLBAR_HIDDEN, shouldHide);
}
}
// Copyright 2017 The Chromium Authors. All rights reserved. // Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
package org.chromium.chrome.browser.browserservices; package org.chromium.chrome.browser.browserservices.trustedwebactivityui.view;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel.PERSISTENT_NOTIFICATION;
import static org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel.PERSISTENT_NOTIFICATION_TAG;
import static org.chromium.chrome.browser.dependency_injection.ChromeCommonQualifiers.APP_CONTEXT; import static org.chromium.chrome.browser.dependency_injection.ChromeCommonQualifiers.APP_CONTEXT;
import static org.chromium.chrome.browser.notifications.NotificationConstants.NOTIFICATION_ID_TWA_PERSISTENT; import static org.chromium.chrome.browser.notifications.NotificationConstants.NOTIFICATION_ID_TWA_PERSISTENT;
...@@ -23,11 +25,16 @@ import android.support.annotation.Nullable; ...@@ -23,11 +25,16 @@ import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider; import org.chromium.chrome.browser.browserservices.BrowserSessionContentUtils;
import org.chromium.chrome.browser.browserservices.Origin;
import org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel;
import org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel.PersistentNotificationData;
import org.chromium.chrome.browser.dependency_injection.ActivityScope; import org.chromium.chrome.browser.dependency_injection.ActivityScope;
import org.chromium.chrome.browser.init.ActivityLifecycleDispatcher; import org.chromium.chrome.browser.init.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.lifecycle.Destroyable; import org.chromium.chrome.browser.lifecycle.Destroyable;
import org.chromium.chrome.browser.lifecycle.StartStopWithNativeObserver; import org.chromium.chrome.browser.modelutil.PropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyObservable;
import org.chromium.chrome.browser.modelutil.PropertyObservable.PropertyObserver;
import org.chromium.chrome.browser.notifications.NotificationBuilderFactory; import org.chromium.chrome.browser.notifications.NotificationBuilderFactory;
import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions; import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
import org.chromium.chrome.browser.preferences.PreferencesLauncher; import org.chromium.chrome.browser.preferences.PreferencesLauncher;
...@@ -37,47 +44,41 @@ import javax.inject.Inject; ...@@ -37,47 +44,41 @@ import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
/** /**
* Publishes and dismisses the notification when running Trusted Web Activities. The notification * Publishes and dismisses the TWA persistent notification.
* offers to manage site data and to share info about it.
*
* The notification is shown while the activity is in started state. It is not removed when the user
* leaves the origin associated with the app by following links.
*/ */
@ActivityScope @ActivityScope
public class PersistentNotificationController implements StartStopWithNativeObserver, Destroyable { public class PersistentNotificationView implements Destroyable, PropertyObserver<PropertyKey> {
private final Context mAppContext; private final Context mAppContext;
private final CustomTabIntentDataProvider mIntentDataProvider; private final TrustedWebActivityModel mModel;
@Nullable
private String mVerifiedPackage;
@Nullable
private Origin mVerifiedOrigin;
private boolean mStarted;
@Nullable @Nullable
private Handler mHandler; private Handler mHandler;
@Inject @Inject
public PersistentNotificationController(@Named(APP_CONTEXT) Context context, public PersistentNotificationView(@Named(APP_CONTEXT) Context context,
ActivityLifecycleDispatcher lifecycleDispatcher, ActivityLifecycleDispatcher lifecycleDispatcher,
CustomTabIntentDataProvider intentDataProvider) { TrustedWebActivityModel model) {
mAppContext = context; mAppContext = context;
mIntentDataProvider = intentDataProvider; mModel = model;
lifecycleDispatcher.register(this); lifecycleDispatcher.register(this);
model.addObserver(this);
} }
@Override @Override
public void onStartWithNative() { public void onPropertyChanged(PropertyObservable<PropertyKey> source,
mStarted = true; @Nullable PropertyKey propertyKey) {
if (mVerifiedPackage != null) { if (propertyKey != PERSISTENT_NOTIFICATION) return;
publish();
PersistentNotificationData data = mModel.get(PERSISTENT_NOTIFICATION);
String tag = mModel.get(PERSISTENT_NOTIFICATION_TAG);
Runnable task;
if (data == null) {
task = new DismissTask(mAppContext, tag);
} else {
task = new PublishTask(tag, data.origin,
mAppContext, data.customTabActivityIntent);
} }
} postToBackgroundThread(task);
@Override
public void onStopWithNative() {
mStarted = false;
dismiss();
} }
@Override @Override
...@@ -85,30 +86,6 @@ public class PersistentNotificationController implements StartStopWithNativeObse ...@@ -85,30 +86,6 @@ public class PersistentNotificationController implements StartStopWithNativeObse
killBackgroundThread(); killBackgroundThread();
} }
/**
* Called when the relationship between an origin and an app with given package name has been
* verified.
*/
public void onOriginVerifiedForPackage(Origin origin, String packageName) {
if (packageName.equals(mVerifiedPackage)) {
return;
}
mVerifiedPackage = packageName;
mVerifiedOrigin = origin;
if (mStarted) {
publish();
}
}
private void publish() {
postToBackgroundThread(new PublishTask(
mVerifiedPackage, mVerifiedOrigin, mAppContext, mIntentDataProvider.getIntent()));
}
private void dismiss() {
postToBackgroundThread(new DismissTask(mAppContext, mVerifiedPackage));
}
private void postToBackgroundThread(Runnable task) { private void postToBackgroundThread(Runnable task) {
if (mHandler == null) { if (mHandler == null) {
HandlerThread backgroundThread = new HandlerThread("TwaPersistentNotification"); HandlerThread backgroundThread = new HandlerThread("TwaPersistentNotification");
......
...@@ -2,13 +2,21 @@ ...@@ -2,13 +2,21 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
package org.chromium.chrome.browser.browserservices; package org.chromium.chrome.browser.browserservices.trustedwebactivityui.view;
import static org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel.DISCLOSURE_EVENTS_CALLBACK;
import static org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel.DISCLOSURE_STATE;
import static org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel.DISCLOSURE_STATE_NOT_SHOWN;
import static org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel.DISCLOSURE_STATE_SHOWN;
import android.content.res.Resources; import android.content.res.Resources;
import android.support.annotation.Nullable;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel;
import org.chromium.chrome.browser.dependency_injection.ActivityScope; import org.chromium.chrome.browser.dependency_injection.ActivityScope;
import org.chromium.chrome.browser.preferences.ChromePreferenceManager; import org.chromium.chrome.browser.modelutil.PropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyObservable;
import org.chromium.chrome.browser.snackbar.Snackbar; import org.chromium.chrome.browser.snackbar.Snackbar;
import org.chromium.chrome.browser.snackbar.SnackbarManager; import org.chromium.chrome.browser.snackbar.SnackbarManager;
...@@ -17,20 +25,18 @@ import javax.inject.Inject; ...@@ -17,20 +25,18 @@ import javax.inject.Inject;
import dagger.Lazy; import dagger.Lazy;
/** /**
* Shows the Trusted Web Activity disclosure when appropriate and records its acceptance. * Shows the Trusted Web Activity disclosure when appropriate and notifies of its acceptance.
* *
* Lifecycle: There should be a 1-1 relationship between this class and * Lifecycle: There should be a 1-1 relationship between this class and instances of Trusted
* {@link TrustedWebActivityUi}. * Web Activities.
* Thread safety: All methods on this class should be called on the UI thread. * Thread safety: All methods on this class should be called on the UI thread.
*/ */
@ActivityScope @ActivityScope
public class TrustedWebActivityDisclosure { public class TrustedWebActivityDisclosureView implements
// TODO(peconn): Make this package private once TrustedWebActivityUi can be injected. PropertyObservable.PropertyObserver<PropertyKey> {
private final Resources mResources; private final Resources mResources;
private final ChromePreferenceManager mPreferenceManager;
private final Lazy<SnackbarManager> mSnackbarManager; private final Lazy<SnackbarManager> mSnackbarManager;
private final TrustedWebActivityModel mModel;
private boolean mSnackbarShowing;
/** /**
* A {@link SnackbarManager.SnackbarController} that records the users acceptance of the * A {@link SnackbarManager.SnackbarController} that records the users acceptance of the
...@@ -43,52 +49,45 @@ public class TrustedWebActivityDisclosure { ...@@ -43,52 +49,45 @@ public class TrustedWebActivityDisclosure {
new SnackbarManager.SnackbarController() { new SnackbarManager.SnackbarController() {
/** /**
* To be called when the user accepts the Running in Chrome disclosure. * To be called when the user accepts the Running in Chrome disclosure.
* @param actionData The package name of the client, as a String.
*/ */
@Override @Override
public void onAction(Object actionData) { public void onAction(Object actionData) {
mPreferenceManager.setUserAcceptedTwaDisclosureForPackage((String) actionData); mModel.get(DISCLOSURE_EVENTS_CALLBACK).onDisclosureAccepted();
} }
}; };
@Inject @Inject
/* package */ TrustedWebActivityDisclosure(Resources resources, TrustedWebActivityDisclosureView(Resources resources,
ChromePreferenceManager preferenceManager, Lazy<SnackbarManager> snackbarManager) { Lazy<SnackbarManager> snackbarManager, TrustedWebActivityModel model) {
mResources = resources; mResources = resources;
mPreferenceManager = preferenceManager;
mSnackbarManager = snackbarManager; mSnackbarManager = snackbarManager;
mModel = model;
mModel.addObserver(this);
} }
/** Dismisses the disclosure if it is showing. */ @Override
/* package */ void dismiss() { public void onPropertyChanged(PropertyObservable<PropertyKey> source,
if (!mSnackbarShowing) return; @Nullable PropertyKey propertyKey) {
if (propertyKey != DISCLOSURE_STATE) return;
mSnackbarManager.get().dismissSnackbars(mSnackbarController);
mSnackbarShowing = false; switch (mModel.get(DISCLOSURE_STATE)) {
} case DISCLOSURE_STATE_SHOWN:
mSnackbarManager.get().showSnackbar(makeRunningInChromeInfobar());
/** Shows the disclosure if it is not already showing and hasn't been accepted. */ break;
/* package */ void showIfNeeded(String packageName) { case DISCLOSURE_STATE_NOT_SHOWN:
if (mSnackbarShowing) return; mSnackbarManager.get().dismissSnackbars(mSnackbarController);
if (wasDismissed(packageName)) return; break;
}
mSnackbarManager.get().showSnackbar(makeRunningInChromeInfobar(packageName));
mSnackbarShowing = true;
}
/** Has a disclosure been dismissed for this client package before? */
private boolean wasDismissed(String packageName) {
return mPreferenceManager.hasUserAcceptedTwaDisclosureForPackage(packageName);
} }
private Snackbar makeRunningInChromeInfobar(String packageName) { private Snackbar makeRunningInChromeInfobar() {
String title = mResources.getString(R.string.twa_running_in_chrome); String title = mResources.getString(R.string.twa_running_in_chrome);
int type = Snackbar.TYPE_PERSISTENT; int type = Snackbar.TYPE_PERSISTENT;
int code = Snackbar.UMA_TWA_PRIVACY_DISCLOSURE; int code = Snackbar.UMA_TWA_PRIVACY_DISCLOSURE;
String action = mResources.getString(R.string.ok_got_it); String action = mResources.getString(R.string.ok_got_it);
return Snackbar.make(title, mSnackbarController, type, code) return Snackbar.make(title, mSnackbarController, type, code)
.setAction(action, packageName) .setAction(action, null)
.setSingleLine(false); .setSingleLine(false);
} }
} }
// Copyright 2018 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.browserservices.trustedwebactivityui.view;
import static org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel.TOOLBAR_HIDDEN;
import org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel;
import org.chromium.chrome.browser.customtabs.CustomTabBrowserControlsVisibilityDelegate;
import org.chromium.chrome.browser.dependency_injection.ActivityScope;
import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
import org.chromium.chrome.browser.fullscreen.FullscreenManager;
import org.chromium.chrome.browser.modelutil.PropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyObservable;
import javax.inject.Inject;
/**
* Hides and shows the toolbar according to the state of the Trusted Web Activity.
*/
@ActivityScope
public class TrustedWebActivityToolbarView implements
PropertyObservable.PropertyObserver<PropertyKey> {
private final ChromeFullscreenManager mFullscreenManager;
private final CustomTabBrowserControlsVisibilityDelegate mControlsVisibilityDelegate;
private final TrustedWebActivityModel mModel;
private int mControlsHidingToken = FullscreenManager.INVALID_TOKEN;
@Inject
public TrustedWebActivityToolbarView(ChromeFullscreenManager fullscreenManager,
CustomTabBrowserControlsVisibilityDelegate controlsVisibilityDelegate,
TrustedWebActivityModel model) {
mFullscreenManager = fullscreenManager;
mControlsVisibilityDelegate = controlsVisibilityDelegate;
mModel = model;
mModel.addObserver(this);
}
@Override
public void onPropertyChanged(PropertyObservable<PropertyKey> observable, PropertyKey key) {
if (key != TOOLBAR_HIDDEN) return;
boolean hide = mModel.get(TOOLBAR_HIDDEN);
mControlsVisibilityDelegate.setTrustedWebActivityMode(hide);
if (hide) {
mControlsHidingToken =
mFullscreenManager.hideAndroidControlsAndClearOldToken(mControlsHidingToken);
} else {
mFullscreenManager.releaseAndroidControlsHidingToken(mControlsHidingToken);
// Force showing the controls for a bit when leaving Trusted Web Activity mode.
mFullscreenManager.getBrowserVisibilityDelegate().showControlsTransient();
}
}
}
...@@ -270,7 +270,7 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent ...@@ -270,7 +270,7 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent
super.preInflationStartup(); super.preInflationStartup();
if (mIntentDataProvider.isTrustedWebActivity()) { if (mIntentDataProvider.isTrustedWebActivity()) {
getComponent().resolveTrustedWebActivityUi(); getComponent().resolveTrustedWebActivityCoordinator();
} }
mSession = mIntentDataProvider.getSession(); mSession = mIntentDataProvider.getSession();
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
package org.chromium.chrome.browser.customtabs.dependency_injection; package org.chromium.chrome.browser.customtabs.dependency_injection;
import org.chromium.chrome.browser.browserservices.TrustedWebActivityUi; import org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityCoordinator;
import org.chromium.chrome.browser.contextual_suggestions.ContextualSuggestionsModule; import org.chromium.chrome.browser.contextual_suggestions.ContextualSuggestionsModule;
import org.chromium.chrome.browser.customtabs.CustomTabBrowserControlsVisibilityDelegate; import org.chromium.chrome.browser.customtabs.CustomTabBrowserControlsVisibilityDelegate;
import org.chromium.chrome.browser.dependency_injection.ActivityScope; import org.chromium.chrome.browser.dependency_injection.ActivityScope;
...@@ -21,6 +21,6 @@ import dagger.Subcomponent; ...@@ -21,6 +21,6 @@ import dagger.Subcomponent;
CustomTabActivityModule.class}) CustomTabActivityModule.class})
@ActivityScope @ActivityScope
public interface CustomTabActivityComponent extends ChromeActivityComponent { public interface CustomTabActivityComponent extends ChromeActivityComponent {
TrustedWebActivityUi resolveTrustedWebActivityUi(); TrustedWebActivityCoordinator resolveTrustedWebActivityCoordinator();
CustomTabBrowserControlsVisibilityDelegate resolveControlsVisibilityDelegate(); CustomTabBrowserControlsVisibilityDelegate resolveControlsVisibilityDelegate();
} }
...@@ -177,20 +177,26 @@ chrome_java_sources = [ ...@@ -177,20 +177,26 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/browserservices/ClearDataNotificationPublisher.java", "java/src/org/chromium/chrome/browser/browserservices/ClearDataNotificationPublisher.java",
"java/src/org/chromium/chrome/browser/browserservices/ClearDataService.java", "java/src/org/chromium/chrome/browser/browserservices/ClearDataService.java",
"java/src/org/chromium/chrome/browser/browserservices/ClientAppBroadcastReceiver.java", "java/src/org/chromium/chrome/browser/browserservices/ClientAppBroadcastReceiver.java",
"java/src/org/chromium/chrome/browser/browserservices/ClientAppDataRecorder.java",
"java/src/org/chromium/chrome/browser/browserservices/ClientAppDataRegister.java", "java/src/org/chromium/chrome/browser/browserservices/ClientAppDataRegister.java",
"java/src/org/chromium/chrome/browser/browserservices/DomainDataCleaner.java", "java/src/org/chromium/chrome/browser/browserservices/DomainDataCleaner.java",
"java/src/org/chromium/chrome/browser/browserservices/Origin.java", "java/src/org/chromium/chrome/browser/browserservices/Origin.java",
"java/src/org/chromium/chrome/browser/browserservices/OriginVerifier.java", "java/src/org/chromium/chrome/browser/browserservices/OriginVerifier.java",
"java/src/org/chromium/chrome/browser/browserservices/PersistentNotificationController.java",
"java/src/org/chromium/chrome/browser/browserservices/PostMessageHandler.java", "java/src/org/chromium/chrome/browser/browserservices/PostMessageHandler.java",
"java/src/org/chromium/chrome/browser/browserservices/Relationship.java", "java/src/org/chromium/chrome/browser/browserservices/Relationship.java",
"java/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityClient.java", "java/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityClient.java",
"java/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityDisclosure.java",
"java/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityOpenTimeRecorder.java",
"java/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityUi.java",
"java/src/org/chromium/chrome/browser/browserservices/UkmRecorder.java", "java/src/org/chromium/chrome/browser/browserservices/UkmRecorder.java",
"java/src/org/chromium/chrome/browser/browserservices/VerificationResultStore.java", "java/src/org/chromium/chrome/browser/browserservices/VerificationResultStore.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/TrustedWebActivityCoordinator.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/TrustedWebActivityModel.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/ClientAppDataRecorder.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/PersistentNotificationController.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityDisclosureController.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityOpenTimeRecorder.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityToolbarController.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityVerifier.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/view/PersistentNotificationView.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/view/TrustedWebActivityDisclosureView.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/view/TrustedWebActivityToolbarView.java",
"java/src/org/chromium/chrome/browser/browsing_data/UrlFilters.java", "java/src/org/chromium/chrome/browser/browsing_data/UrlFilters.java",
"java/src/org/chromium/chrome/browser/childaccounts/ChildAccountFeedbackReporter.java", "java/src/org/chromium/chrome/browser/childaccounts/ChildAccountFeedbackReporter.java",
"java/src/org/chromium/chrome/browser/childaccounts/ChildAccountService.java", "java/src/org/chromium/chrome/browser/childaccounts/ChildAccountService.java",
...@@ -2283,12 +2289,12 @@ chrome_junit_test_java_sources = [ ...@@ -2283,12 +2289,12 @@ chrome_junit_test_java_sources = [
"junit/src/org/chromium/chrome/browser/browserservices/ClearDataNotificationPublisherTest.java", "junit/src/org/chromium/chrome/browser/browserservices/ClearDataNotificationPublisherTest.java",
"junit/src/org/chromium/chrome/browser/browserservices/ClearDataServiceTest.java", "junit/src/org/chromium/chrome/browser/browserservices/ClearDataServiceTest.java",
"junit/src/org/chromium/chrome/browser/browserservices/ClientAppBroadcastReceiverTest.java", "junit/src/org/chromium/chrome/browser/browserservices/ClientAppBroadcastReceiverTest.java",
"junit/src/org/chromium/chrome/browser/browserservices/ClientAppDataRecorderTest.java",
"junit/src/org/chromium/chrome/browser/browserservices/ClientAppDataRegisterTest.java", "junit/src/org/chromium/chrome/browser/browserservices/ClientAppDataRegisterTest.java",
"junit/src/org/chromium/chrome/browser/browserservices/DomainDataCleanerTest.java", "junit/src/org/chromium/chrome/browser/browserservices/DomainDataCleanerTest.java",
"junit/src/org/chromium/chrome/browser/browserservices/OriginTest.java", "junit/src/org/chromium/chrome/browser/browserservices/OriginTest.java",
"junit/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityClientTest.java", "junit/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityClientTest.java",
"junit/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityDisclosureTest.java", "junit/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/ClientAppDataRecorderTest.java",
"junit/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityDisclosureControllerTest.java",
"junit/src/org/chromium/chrome/browser/cached_image_fetcher/CachedImageFetcherTest.java", "junit/src/org/chromium/chrome/browser/cached_image_fetcher/CachedImageFetcherTest.java",
"junit/src/org/chromium/chrome/browser/compositor/animation/CompositorAnimatorTest.java", "junit/src/org/chromium/chrome/browser/compositor/animation/CompositorAnimatorTest.java",
"junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperTest.java", "junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperTest.java",
......
// Copyright 2018 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.browserservices;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.res.Resources;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.browser.preferences.ChromePreferenceManager;
import org.chromium.chrome.browser.snackbar.Snackbar;
import org.chromium.chrome.browser.snackbar.SnackbarManager;
/**
* Tests for {@link TrustedWebActivityDisclosure}.
*/
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class TrustedWebActivityDisclosureTest {
private static final String TWA_PACKAGE = "com.example.twa";
@Mock public Resources mResources;
@Mock public ChromePreferenceManager mPreferences;
@Mock public SnackbarManager mSnackbarManager;
private TrustedWebActivityDisclosure mDisclosure;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
doReturn("any string").when(mResources).getString(anyInt());
doReturn(false).when(mPreferences).hasUserAcceptedTwaDisclosureForPackage(anyString());
mDisclosure = new TrustedWebActivityDisclosure(mResources, mPreferences,
() -> mSnackbarManager);
}
@Test
@Feature("TrustedWebActivities")
public void showIsIdempotent() {
mDisclosure.showIfNeeded(TWA_PACKAGE);
mDisclosure.showIfNeeded(TWA_PACKAGE);
verify(mSnackbarManager).showSnackbar(any());
}
@Test
@Feature("TrustedWebActivities")
public void hideIsIdempotent() {
mDisclosure.showIfNeeded(TWA_PACKAGE);
mDisclosure.dismiss();
mDisclosure.dismiss();
verify(mSnackbarManager).dismissSnackbars(any());
}
@Test
@Feature("TrustedWebActivities")
public void noShowIfAlreadyAccepted() {
doReturn(true).when(mPreferences).hasUserAcceptedTwaDisclosureForPackage(anyString());
mDisclosure.showIfNeeded(TWA_PACKAGE);
verify(mSnackbarManager, times(0)).showSnackbar(any());
}
@Test
@Feature("TrustedWebActivities")
public void recordDismiss() {
ArgumentCaptor<Snackbar> snackbarCaptor = ArgumentCaptor.forClass(Snackbar.class);
mDisclosure.showIfNeeded(TWA_PACKAGE);
verify(mSnackbarManager).showSnackbar(snackbarCaptor.capture());
// Dismiss the Snackbar.
// TODO(peconn): Refactor Snackbar to make this a bit cleaner.
snackbarCaptor.getValue().getController().onAction(
snackbarCaptor.getValue().getActionDataForTesting());
verify(mPreferences).setUserAcceptedTwaDisclosureForPackage(TWA_PACKAGE);
}
@Test
@Feature("TrustedWebActivities")
public void doNothingOnNoSnackbarAction() {
ArgumentCaptor<Snackbar> snackbarCaptor = ArgumentCaptor.forClass(Snackbar.class);
mDisclosure.showIfNeeded(TWA_PACKAGE);
verify(mSnackbarManager).showSnackbar(snackbarCaptor.capture());
// Dismiss the Snackbar.
// TODO(peconn): Refactor Snackbar to make this a bit cleaner.
snackbarCaptor.getValue().getController().onDismissNoAction(
snackbarCaptor.getValue().getActionDataForTesting());
verify(mPreferences, times(0)).setUserAcceptedTwaDisclosureForPackage(anyString());
}
}
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
package org.chromium.chrome.browser.browserservices; package org.chromium.chrome.browser.browserservices.trustedwebactivityui.controller;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
...@@ -29,6 +29,8 @@ import org.robolectric.annotation.Config; ...@@ -29,6 +29,8 @@ import org.robolectric.annotation.Config;
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.ClientAppDataRegister;
import org.chromium.chrome.browser.browserservices.Origin;
import org.chromium.chrome.browser.util.test.ShadowUrlUtilities; import org.chromium.chrome.browser.util.test.ShadowUrlUtilities;
/** /**
......
// Copyright 2018 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.browserservices.trustedwebactivityui.controller;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import static org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel.DISCLOSURE_EVENTS_CALLBACK;
import static org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel.DISCLOSURE_STATE;
import static org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel.DISCLOSURE_STATE_NOT_SHOWN;
import static org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel.DISCLOSURE_STATE_SHOWN;
import static org.chromium.chrome.browser.browserservices.trustedwebactivityui.controller.TrustedWebActivityVerifier.VERIFICATION_FAILURE;
import static org.chromium.chrome.browser.browserservices.trustedwebactivityui.controller.TrustedWebActivityVerifier.VERIFICATION_SUCCESS;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.browser.browserservices.Origin;
import org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel;
import org.chromium.chrome.browser.browserservices.trustedwebactivityui.controller.TrustedWebActivityVerifier.VerificationState;
import org.chromium.chrome.browser.init.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.preferences.ChromePreferenceManager;
/**
* Tests for {@link TrustedWebActivityDisclosureController}.
*/
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class TrustedWebActivityDisclosureControllerTest {
private static final String CLIENT_PACKAGE = "com.example.twaclient";
private static final Origin ORIGIN = new Origin("com.example.twa");
@Mock public ChromePreferenceManager mPreferences;
@Mock public ActivityLifecycleDispatcher mLifecycleDispatcher;
@Mock public TrustedWebActivityVerifier mVerifier;
@Captor public ArgumentCaptor<Runnable> mVerificationObserverCaptor;
public TrustedWebActivityModel mModel = new TrustedWebActivityModel();
private TrustedWebActivityDisclosureController mDisclosureController;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
doReturn(CLIENT_PACKAGE).when(mVerifier).getClientPackageName();
doNothing().when(mVerifier).addVerificationObserver(mVerificationObserverCaptor.capture());
doReturn(false).when(mPreferences).hasUserAcceptedTwaDisclosureForPackage(anyString());
mDisclosureController = new TrustedWebActivityDisclosureController(
mPreferences, mModel, mLifecycleDispatcher, mVerifier);
}
@Test
@Feature("TrustedWebActivities")
public void showsWhenOriginVerified() {
enterVerifiedOrigin();
assertSnackbarShown();
}
@Test
@Feature("TrustedWebActivities")
public void dismissesWhenLeavingVerifiedOrigin() {
enterVerifiedOrigin();
exitVerifiedOrigin();
assertSnackbarNotShown();
}
@Test
@Feature("TrustedWebActivities")
public void showsAgainWhenReenteringTrustedOrigin() {
enterVerifiedOrigin();
exitVerifiedOrigin();
enterVerifiedOrigin();
assertSnackbarShown();
}
@Test
@Feature("TrustedWebActivities")
public void noShowIfAlreadyAccepted() {
doReturn(true).when(mPreferences).hasUserAcceptedTwaDisclosureForPackage(anyString());
enterVerifiedOrigin();
assertSnackbarNotShown();
}
@Test
@Feature("TrustedWebActivities")
public void recordDismiss() {
enterVerifiedOrigin();
dismissSnackbar();
verify(mPreferences).setUserAcceptedTwaDisclosureForPackage(CLIENT_PACKAGE);
}
private void enterVerifiedOrigin() {
setVerificationState(new VerificationState(ORIGIN, VERIFICATION_SUCCESS));
}
private void exitVerifiedOrigin() {
setVerificationState(new VerificationState(ORIGIN, VERIFICATION_FAILURE));
}
private void setVerificationState(VerificationState state) {
doReturn(state).when(mVerifier).getState();
for (Runnable observer : mVerificationObserverCaptor.getAllValues()) {
observer.run();
}
}
private void assertSnackbarShown() {
assertEquals(DISCLOSURE_STATE_SHOWN, mModel.get(DISCLOSURE_STATE));
}
private void assertSnackbarNotShown() {
assertEquals(DISCLOSURE_STATE_NOT_SHOWN, mModel.get(DISCLOSURE_STATE));
}
private void dismissSnackbar() {
mModel.get(DISCLOSURE_EVENTS_CALLBACK).onDisclosureAccepted();
}
}
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