Commit c5bf413b authored by Calder Kitagawa's avatar Calder Kitagawa Committed by Commit Bot

[WebAPKs] Privacy disclosure Snackbar

Adds a Snackbar to disclose when an unbound WebAPK or TWA is running in
Chrome. This is designed such that users see the disclosure under the
following conditions:

- The webapp is a TWA or WebAPK
- If it is a WebAPK it was not installed from Chrome.

Until the user presses "OK, got it" the snackbar will persist and
remains during navigation. If a session is ended without pressing
"OK, got it" then the message will appear the next time the app is
opened or resumed.

This appears once per app installation with the caveat that if a user
clears their Chrome data the app will show this message again.

This change is implemented to only start showing the disclosure when
the storage is null. This happens only when an app is opened for the
first time. As such all deployed and launched unbound WebAPKs will not
see this message. To facilitate this we set a boolean flag to show the
message when we launch with null storage. The reason this is necessary
that the default value for a key is false so for existing
unbound WebAPKs we would try to show the message regardless of state
if we didn't use a set, check, clear approach.

Known issues:
It appears that there is a finch trial for chrome modern that isn't
properly initialized if the WebAPK is launched cold (i.e. Chrome
hasn't been started) and Chrome's data has been wiped. As a result,
the old black snackbar is shown instead of the white one. This is
remedied if the app is relaunched and won't occur if Chrome is launched
before the WebAPK.

Bug: 862401
Change-Id: Ieb09b7b628d5718908f8841b06b469c54ed59472
Reviewed-on: https://chromium-review.googlesource.com/1142224
Commit-Queue: Calder Kitagawa <ckitagawa@chromium.org>
Reviewed-by: default avatarPeter Conn <peconn@chromium.org>
Reviewed-by: default avatarTheresa <twellington@chromium.org>
Reviewed-by: default avatarDominick Ng <dominickn@chromium.org>
Reviewed-by: default avatarXi Han <hanxi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#582187}
parent 15417f9f
......@@ -999,9 +999,6 @@ by a child template that "extends" this file.
android:exported="false"/>
<service android:name="org.chromium.chrome.browser.incognito.IncognitoNotificationService"
android:exported="false"/>
<service android:name="org.chromium.chrome.browser.webapps.WebApkDisclosureNotificationService"
android:exported="false"/>
<!-- The following service entries exist in order to allow us to
start more than one sandboxed process. -->
......
......@@ -395,7 +395,6 @@ public class OriginVerifier {
ThreadUtils.assertOnUiThread();
if (sPackageToCachedOrigins != null) sPackageToCachedOrigins.clear();
ChromePreferenceManager.getInstance().setVerifiedDigitalAssetLinks(Collections.emptySet());
ChromePreferenceManager.getInstance().clearAllTrustedWebActivityLastDisclosureTimes();
}
private native long nativeInit(Profile profile);
......
// 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 android.content.Context;
import org.chromium.base.CommandLine;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.preferences.ChromePreferenceManager;
import org.chromium.ui.widget.Toast;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* Since Trusted Web Activities are part of Chrome they have access to the cookie jar and have the
* same reporting and metrics as other parts of Chrome. However, they have no UI making the fact
* they are part of Chrome obvious to the user. Therefore we show a disclosure Toast whenever an
* app opens a Trusted Web Activity, at most once per week per app.
*/
public class TrustedWebActivityDisclosure {
private static final int DISCLOSURE_PERIOD_DAYS = 7;
/**
* Show the "Running in Chrome" disclosure (Toast) if one hasn't been shown recently.
*/
public static void showIfNeeded(Context context, String packageName) {
ChromePreferenceManager prefs = ChromePreferenceManager.getInstance();
Date now = new Date();
Date lastShown = prefs.getTrustedWebActivityLastDisclosureTime(packageName);
long millisSince = now.getTime() - lastShown.getTime();
long daysSince = TimeUnit.DAYS.convert(millisSince, TimeUnit.MILLISECONDS);
boolean force = CommandLine.getInstance().hasSwitch(
ChromeSwitches.FORCE_TRUSTED_WEB_ACTIVITY_DISCLOSURE);
if (!force && daysSince <= DISCLOSURE_PERIOD_DAYS) return;
prefs.setTrustedWebActivityLastDisclosureTime(packageName, now);
String disclosure = context.getResources().getString(R.string.twa_running_in_chrome);
Toast.makeText(context, disclosure, Toast.LENGTH_LONG).show();
}
private TrustedWebActivityDisclosure() {}
}
......@@ -10,7 +10,6 @@ import org.chromium.base.ContextUtils;
import org.chromium.chrome.browser.crash.MinidumpUploadService.ProcessType;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
......@@ -183,8 +182,6 @@ public class ChromePreferenceManager {
private static final String VERIFIED_DIGITAL_ASSET_LINKS =
"verified_digital_asset_links";
private static final String TRUSTED_WEB_ACTIVITY_LAST_DISCLOSURE_TIME =
"trusted_web_activity_last_disclosure_time:";
/**
* Whether VR assets component should be registered on startup.
......@@ -463,43 +460,6 @@ public class ChromePreferenceManager {
mSharedPreferences.edit().putStringSet(VERIFIED_DIGITAL_ASSET_LINKS, links).apply();
}
/**
* Private convenience method to create a Preferences Key to hold the last time the given
* package displayed a "Running in Chrome" disclosure while opening a Trusted Web Activity.
*/
private static String getTrustedWebActivityDisclosureTimeKey(String packageName) {
return TRUSTED_WEB_ACTIVITY_LAST_DISCLOSURE_TIME + packageName;
}
/**
* Gets the last time a disclosure was shown while opening a Trusted Web Activity for the given
* package. Returns a Date object representing the Unix epoch if no data was found.
*/
public Date getTrustedWebActivityLastDisclosureTime(String packageName) {
return new Date(readLong(getTrustedWebActivityDisclosureTimeKey(packageName), 0));
}
/**
* Sets the last time a disclosure was shown while opening a Trusted Web Activity for the given
* package.
*/
public void setTrustedWebActivityLastDisclosureTime(String packageName, Date time) {
writeLong(getTrustedWebActivityDisclosureTimeKey(packageName), time.getTime());
}
/**
* Wipes all recordings of the last disclosure times for packages opening TWAs.
*/
public void clearAllTrustedWebActivityLastDisclosureTimes() {
SharedPreferences.Editor ed = mSharedPreferences.edit();
for (String key : mSharedPreferences.getAll().keySet()) {
if (!key.startsWith(TRUSTED_WEB_ACTIVITY_LAST_DISCLOSURE_TIME)) continue;
ed.remove(key);
}
ed.apply();
}
/**
* Writes the given int value to the named shared preference.
* @param key The name of the preference to modify.
......
......@@ -74,6 +74,7 @@ public class Snackbar {
public static final int UMA_MISSING_FILES_NO_SD_CARD = 24;
public static final int UMA_OFFLINE_INDICATOR = 25;
public static final int UMA_FEED_NTP_STREAM = 26;
public static final int UMA_WEBAPK_TWA_PRIVACY_DISCLOSURE = 27;
private SnackbarController mController;
private CharSequence mText;
......
// Copyright 2017 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 android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.support.v4.app.NotificationCompat;
import org.chromium.base.ActivityState;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.ContextUtils;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.notifications.ChromeNotificationBuilder;
import org.chromium.chrome.browser.notifications.NotificationBuilderFactory;
import org.chromium.chrome.browser.notifications.NotificationUmaTracker;
import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
import java.util.HashSet;
import java.util.Set;
/**
* Manages the notification indicating that a WebApk is backed by chrome code and may share data.
* It's shown while an Unbound WebApk is displayed in the foreground until the user dismisses it.
*/
public class WebApkDisclosureNotificationManager {
// We always use the same integer id when showing and closing notifications. The notification
// tag is always set, which is a safe and sufficient way of identifying a notification, so the
// integer id is not needed anymore except it must not vary in an uncontrolled way.
private static final int PLATFORM_ID = 100;
// Prefix used for generating a unique notification tag.
private static final String DISMISSAL_NOTIFICATION_TAG_PREFIX =
"dismissal_notification_tag_prefix.";
/** Records whether we're currently showing a disclosure notification. */
private static Set<String> sVisibleNotifications = new HashSet<>();
/**
* For Trusted Web Activity show a notification that it's running in Chrome.
*/
static void maybeShowDisclosure(WebappActivity activity, WebappDataStorage storage) {
String packageName = activity.getNativeClientPackageName();
boolean isTWA = (activity.getActivityType() == WebappActivity.ActivityType.TWA);
boolean isNotificationAllowed = !storage.hasDismissedDisclosure()
&& !sVisibleNotifications.contains(packageName)
&& !WebappActionsNotificationManager.isEnabled();
if (!isTWA || !isNotificationAllowed) return;
int activityState = ApplicationStatus.getStateForActivity(activity);
if (activityState == ActivityState.STARTED || activityState == ActivityState.RESUMED
|| activityState == ActivityState.PAUSED) {
sVisibleNotifications.add(packageName);
WebApkDisclosureNotificationManager.showDisclosure(activity.getWebappInfo());
}
}
/**
* Shows the privacy disclosure informing the user that Chrome is being used.
* @param webappInfo Web App this is currently displayed fullscreen.
*/
private static void showDisclosure(WebappInfo webappInfo) {
Context context = ContextUtils.getApplicationContext();
ChromeNotificationBuilder builder =
NotificationBuilderFactory.createChromeNotificationBuilder(
false /* preferCompat */, ChannelDefinitions.ChannelId.BROWSER);
builder.setContentTitle(webappInfo.name())
.setPriorityBeforeO(NotificationCompat.PRIORITY_MIN)
.setSmallIcon(R.drawable.ic_chrome)
.setLargeIcon(webappInfo.icon())
.setDeleteIntent(WebApkDisclosureNotificationService.getDeleteIntent(
context, webappInfo.id()))
.setContentText(context.getResources().getString(
R.string.webapk_running_in_chrome_disclosure));
NotificationManager nm =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification = builder.build();
nm.notify(DISMISSAL_NOTIFICATION_TAG_PREFIX + webappInfo.apkPackageName(), PLATFORM_ID,
notification);
NotificationUmaTracker.getInstance().onNotificationShown(
NotificationUmaTracker.SystemNotificationType.WEBAPK, notification);
}
/**
* Dismisses the notification.
* @param activity Web App this is currently displayed fullscreen.
*/
public static void dismissNotification(WebappActivity activity) {
String packageName = activity.getNativeClientPackageName();
if (!sVisibleNotifications.contains(packageName)) return;
Context context = ContextUtils.getApplicationContext();
NotificationManager nm =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
nm.cancel(DISMISSAL_NOTIFICATION_TAG_PREFIX + packageName, PLATFORM_ID);
sVisibleNotifications.remove(packageName);
}
}
// Copyright 2017 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 android.app.IntentService;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
/**
* Service that handles the action of clicking on the WebApk disclosure notification.
*/
public class WebApkDisclosureNotificationService extends IntentService {
private static final String TAG = "WebApkDisclosureNotificationService";
private static final String ACTION_HIDE_DISCLOSURE =
"org.chromium.chrome.browser.webapps.HIDE_DISCLOSURE";
private static final String EXTRA_WEBAPP_ID = "webapp_id";
static PendingIntent getDeleteIntent(Context context, String webApkPackageName) {
Intent intent = new Intent(context, WebApkDisclosureNotificationService.class);
intent.setAction(ACTION_HIDE_DISCLOSURE);
intent.putExtra(EXTRA_WEBAPP_ID, webApkPackageName);
return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
/** Empty public constructor needed by Android. */
public WebApkDisclosureNotificationService() {
super(TAG);
}
@Override
protected void onHandleIntent(Intent intent) {
String webappId = intent.getStringExtra(EXTRA_WEBAPP_ID);
WebappDataStorage storage = WebappRegistry.getInstance().getWebappDataStorage(webappId);
if (storage != null) storage.setDismissedDisclosure();
}
}
......@@ -46,7 +46,6 @@ import org.chromium.chrome.browser.browserservices.BrowserSessionContentHandler;
import org.chromium.chrome.browser.browserservices.BrowserSessionContentUtils;
import org.chromium.chrome.browser.browserservices.BrowserSessionDataProvider;
import org.chromium.chrome.browser.browserservices.Origin;
import org.chromium.chrome.browser.browserservices.TrustedWebActivityDisclosure;
import org.chromium.chrome.browser.browserservices.UkmRecorder;
import org.chromium.chrome.browser.browserservices.VerificationState;
import org.chromium.chrome.browser.compositor.layouts.LayoutManager;
......@@ -117,6 +116,8 @@ public class WebappActivity extends SingleTabActivity {
private WebappSplashScreenController mSplashController;
private WebappDisclosureSnackbarController mDisclosureSnackbarController;
private boolean mIsInitialized;
private long mOnResumeTimestampMs;
private Integer mBrandColor;
......@@ -189,6 +190,7 @@ public class WebappActivity extends SingleTabActivity {
mWebappInfo = createWebappInfo(null);
mDirectoryManager = new WebappDirectoryManager();
mSplashController = new WebappSplashScreenController();
mDisclosureSnackbarController = new WebappDisclosureSnackbarController();
mNotificationManager = new WebappActionsNotificationManager(this);
}
......@@ -392,12 +394,6 @@ public class WebappActivity extends SingleTabActivity {
super.onStartWithNative();
BrowserSessionContentUtils.setActiveContentHandler(mTrustedWebContentProvider);
mDirectoryManager.cleanUpDirectories(this, getActivityId());
// If WebappStorage is available, check whether to show a disclosure notification. If it's
// not available, this check will happen once deferred startup returns with the storage
// instance.
WebappDataStorage storage =
WebappRegistry.getInstance().getWebappDataStorage(mWebappInfo.id());
if (storage != null) WebApkDisclosureNotificationManager.maybeShowDisclosure(this, storage);
}
@Override
......@@ -409,7 +405,6 @@ public class WebappActivity extends SingleTabActivity {
if (getFullscreenManager() != null) {
getFullscreenManager().exitPersistentFullscreenMode();
}
WebApkDisclosureNotificationManager.dismissNotification(this);
}
/**
......@@ -507,6 +502,11 @@ public class WebappActivity extends SingleTabActivity {
public void onResumeWithNative() {
super.onResumeWithNative();
mNotificationManager.maybeShowNotification();
WebappDataStorage storage =
WebappRegistry.getInstance().getWebappDataStorage(mWebappInfo.id());
if (storage != null) {
mDisclosureSnackbarController.maybeShowDisclosure(this, storage, false /* force */);
}
}
@Override
......@@ -543,7 +543,6 @@ public class WebappActivity extends SingleTabActivity {
protected void onDeferredStartupWithStorage(WebappDataStorage storage) {
updateStorage(storage);
WebApkDisclosureNotificationManager.maybeShowDisclosure(this, storage);
}
protected void onDeferredStartupWithNullStorage() {
......@@ -556,6 +555,13 @@ public class WebappActivity extends SingleTabActivity {
@Override
public void onWebappDataStorageRetrieved(WebappDataStorage storage) {
onDeferredStartupWithStorage(storage);
// Set force == true to indicate that we need to show a privacy
// disclosure for the newly installed TWAs and 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.
mDisclosureSnackbarController.maybeShowDisclosure(
WebappActivity.this, storage, true /* force */);
}
});
}
......@@ -598,7 +604,6 @@ public class WebappActivity extends SingleTabActivity {
}
BrowserServicesMetrics.recordTwaOpened();
TrustedWebActivityDisclosure.showIfNeeded(this, packageName);
// When verification occurs instantly (eg the result is cached) then it returns
// before there is an active tab.
......
......@@ -69,8 +69,8 @@ public class WebappDataStorage {
// The shell Apk version requested in the last update.
static final String KEY_LAST_REQUESTED_SHELL_APK_VERSION = "last_requested_shell_apk_version";
// Whether the user has dismissed the disclosure UI.
static final String KEY_DISMISSED_DISCLOSURE = "dismissed_dislosure";
// Whether to show the user the Snackbar disclosure UI.
static final String KEY_SHOW_DISCLOSURE = "show_disclosure";
// The path where serialized update data is written before uploading to the WebAPK server.
static final String KEY_PENDING_UPDATE_FILE_PATH = "pending_update_file_path";
......@@ -335,7 +335,7 @@ public class WebappDataStorage {
editor.remove(KEY_LAST_UPDATE_REQUEST_COMPLETE_TIME);
editor.remove(KEY_DID_LAST_UPDATE_REQUEST_SUCCEED);
editor.remove(KEY_RELAX_UPDATES);
editor.remove(KEY_DISMISSED_DISCLOSURE);
editor.remove(KEY_SHOW_DISCLOSURE);
editor.apply();
}
......@@ -458,12 +458,30 @@ public class WebappDataStorage {
return mPreferences.getBoolean(KEY_DID_LAST_UPDATE_REQUEST_SUCCEED, false);
}
void setDismissedDisclosure() {
mPreferences.edit().putBoolean(KEY_DISMISSED_DISCLOSURE, true).apply();
/**
* Returns whether to show the user a privacy disclosure (used for TWAs and unbound WebAPKs).
* This is not cleared until the user explicitly acknowledges it.
*/
boolean shouldShowDisclosure() {
return mPreferences.getBoolean(KEY_SHOW_DISCLOSURE, false);
}
/**
* Clears the show disclosure bit, this stops TWAs and unbound WebAPKs from showing a privacy
* disclosure on every resume of the Webapp. This should be called when the user has
* acknowledged the disclosure.
*/
void clearShowDisclosure() {
mPreferences.edit().putBoolean(KEY_SHOW_DISCLOSURE, false).apply();
}
boolean hasDismissedDisclosure() {
return mPreferences.getBoolean(KEY_DISMISSED_DISCLOSURE, false);
/**
* Sets the disclosure bit which causes TWAs and unbound WebAPKs to show a privacy disclosure.
* This is set the first time an app is opened without storage (either right after install or
* after Chrome's storage is cleared).
*/
void setShowDisclosure() {
mPreferences.edit().putBoolean(KEY_SHOW_DISCLOSURE, true).apply();
}
/** Updates the shell Apk version requested in the last update. */
......
// 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.webapps;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.snackbar.Snackbar;
import org.chromium.chrome.browser.snackbar.SnackbarManager;
import org.chromium.webapk.lib.common.WebApkConstants;
/**
* Trusted Web Activities and 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 purposes we show a Snackbar based privacy disclosure that the
* activity is running as part of Chrome. This occurs once per app installation, but will appear
* again if Chrome's storage is cleared. The Snackbar must be acknowledged in order to be dismissed
* and should remain onscreen 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.
*/
public class WebappDisclosureSnackbarController implements SnackbarManager.SnackbarController {
/**
* @param actionData an instance of WebappInfo
*/
@Override
public void onAction(Object actionData) {
if (actionData instanceof WebappDataStorage) {
((WebappDataStorage) actionData).clearShowDisclosure();
}
}
/**
* Stub expected by SnackbarController.
*/
@Override
public void onDismissNoAction(Object actionData) {}
/**
* 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 force Whether to force showing the Snackbar (if no storage is available on start).
*/
public void maybeShowDisclosure(
WebappActivity activity, WebappDataStorage storage, boolean force) {
if (storage == null) return;
// If forced we set the bit to show the disclosure. This persists to future instances.
if (force) storage.setShowDisclosure();
if (shouldShowDisclosure(activity, storage)) {
activity.getSnackbarManager().showSnackbar(
Snackbar.make(activity.getResources().getString(
R.string.app_running_in_chrome_disclosure),
this, Snackbar.TYPE_PERSISTENT,
Snackbar.UMA_WEBAPK_TWA_PRIVACY_DISCLOSURE)
.setAction(
activity.getResources().getString(R.string.ok_got_it), storage)
.setSingleLine(false));
}
}
/**
* Restricts showing to TWAs and unbound WebAPKs that haven't dismissed the disclosure.
* @param activity Webapp activity.
* @param storage Storage for the Webapp.
* @return boolean indicating whether to show the privacy disclosure.
*/
private boolean shouldShowDisclosure(WebappActivity activity, WebappDataStorage storage) {
// Show only if the correct flag is set.
if (!storage.shouldShowDisclosure()) {
return false;
}
int activityType = activity.getActivityType();
// Show for TWAs.
if (activityType == WebappActivity.ActivityType.TWA) {
return true;
}
String packageName = activity.getNativeClientPackageName();
// Show for unbound WebAPKs.
if (activityType == WebappActivity.ActivityType.WEBAPK
&& (packageName != null
&& !packageName.startsWith(WebApkConstants.WEBAPK_PACKAGE_PREFIX))) {
return true;
}
// Webapps or bound WebAPKs.
return false;
}
}
\ No newline at end of file
......@@ -3427,7 +3427,7 @@ However, you aren’t invisible. Going private doesn’t hide your browsing from
</message>
<!-- WebAPK/TWA related strings -->
<message name="IDS_WEBAPK_RUNNING_IN_CHROME_DISCLOSURE" desc="Message on the notification that indicates a WebApk may use Chrome data.">
<message name="IDS_APP_RUNNING_IN_CHROME_DISCLOSURE" desc="Message on the snackbar that indicates an app may use Chrome data.">
This app is running in Chrome.
</message>
<message name="IDS_WEBAPP_TAP_TO_COPY_URL" desc="Message on the notification that indicates that taping it will copy a Web App's URL into the clipboard.">
......@@ -3448,9 +3448,6 @@ However, you aren’t invisible. Going private doesn’t hide your browsing from
<message name="IDS_WEBAPP_NETWORK_ERROR_MESSAGE_TUNNEL_CONNECTION_FAILED" desc="The error message for ERROR_TUNNEL_CONNECTION_FAILED.">
Establishing a tunnel via proxy server failed
</message>
<message name="IDS_TWA_RUNNING_IN_CHROME" desc="Disclosure on opening a TWA that it may use Chrome data. Shown on a Toast." translateable="false">
Running in Chrome
</message>
<!-- Keyboard shortcuts in Android N-->
<message name="IDS_KEYBOARD_SHORTCUT_OPEN_NEW_TAB" desc="A text label that appears next to the keyboard shortcut to open a new tab in Chrome. The shortcut description is shown in a system dialog along with all other supported shortcuts. [CHAR-LIMIT=55]">
......
......@@ -167,7 +167,6 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/browserservices/OriginVerifier.java",
"java/src/org/chromium/chrome/browser/browserservices/PostMessageHandler.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/TrustedWebActivityUi.java",
"java/src/org/chromium/chrome/browser/browserservices/UkmRecorder.java",
"java/src/org/chromium/chrome/browser/browserservices/VerificationState.java",
......@@ -1494,8 +1493,6 @@ chrome_java_sources = [
"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/WebApkActivity9.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkDisclosureNotificationManager.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkDisclosureNotificationService.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkInfo.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkInstaller.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkInstallService.java",
......@@ -1524,6 +1521,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java",
"java/src/org/chromium/chrome/browser/webapps/WebappDelegateFactory.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/WebappInfo.java",
"java/src/org/chromium/chrome/browser/webapps/WebappLauncherActivity.java",
"java/src/org/chromium/chrome/browser/webapps/WebappManagedActivity.java",
......@@ -2282,6 +2280,7 @@ chrome_junit_test_java_sources = [
"junit/src/org/chromium/chrome/browser/webapps/MockWebappDataStorageClockRule.java",
"junit/src/org/chromium/chrome/browser/webapps/WebappDataStorageTest.java",
"junit/src/org/chromium/chrome/browser/webapps/WebappDirectoryManagerTest.java",
"junit/src/org/chromium/chrome/browser/webapps/WebappDisclosureSnackbarControllerTest.java",
"junit/src/org/chromium/chrome/browser/webapps/WebappRegistryTest.java",
"junit/src/org/chromium/chrome/browser/webapps/WebappInfoTest.java",
"junit/src/org/chromium/chrome/browser/webapps/WebApkInfoTest.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.webapps;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
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.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.snackbar.Snackbar;
import org.chromium.chrome.browser.snackbar.SnackbarManager;
import org.chromium.webapk.lib.common.WebApkConstants;
/**
* Tests for WebappDisclosureSnackbarController
*/
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class WebappDisclosureSnackbarControllerTest {
@Mock
public WebappActivity mActivity;
@Mock
public SnackbarManager mManager;
@Mock
public Resources mResources;
@Before
public void setUp() throws InterruptedException {
MockitoAnnotations.initMocks(this);
doReturn("test text").when(mResources).getString(anyInt());
doReturn(mManager).when(mActivity).getSnackbarManager();
doReturn(mResources).when(mActivity).getResources();
}
public void verifyShownThenDismissedOnNewCreateStorage(String packageName) {
WebappDisclosureSnackbarController controller = new WebappDisclosureSnackbarController();
WebappDataStorage storage = WebappDataStorage.open(packageName);
// Simulates the case that shows the disclosure when creating a new storage.
controller.maybeShowDisclosure(mActivity, storage, true);
verify(mManager, times(1)).showSnackbar(any(Snackbar.class));
assertTrue(storage.shouldShowDisclosure());
// Simulate a restart or a resume (has storage so `force = false`).
controller.maybeShowDisclosure(mActivity, storage, false);
verify(mManager, times(2)).showSnackbar(any(Snackbar.class));
assertTrue(storage.shouldShowDisclosure());
// Dismiss the disclosure.
controller.onAction(storage);
// Simulate resuming or starting again this time no disclosure should show.
assertFalse(storage.shouldShowDisclosure());
controller.maybeShowDisclosure(mActivity, storage, false);
verify(mManager, times(2)).showSnackbar(any(Snackbar.class));
storage.delete();
}
public void verifyNotShownOnExistingStorageWithoutShouldShowDisclosure(String packageName) {
WebappDisclosureSnackbarController controller = new WebappDisclosureSnackbarController();
WebappDataStorage storage = WebappDataStorage.open(packageName);
// Simulate that starting with existing storage will not cause the disclosure to show.
assertFalse(storage.shouldShowDisclosure());
controller.maybeShowDisclosure(mActivity, storage, false);
verify(mManager, times(0)).showSnackbar(any(Snackbar.class));
storage.delete();
}
public void verifyNeverShown(String packageName) {
WebappDisclosureSnackbarController controller = new WebappDisclosureSnackbarController();
WebappDataStorage storage = WebappDataStorage.open(packageName);
// Try to show the disclosure the first time (fake having no storage on startup by setting
// `force = true`) this shouldn't work as the app was installed via Chrome.
controller.maybeShowDisclosure(mActivity, storage, true);
verify(mManager, times(0)).showSnackbar(any(Snackbar.class));
// Try to the disclosure again this time emulating a restart or a resume (fake having
// storage `force = false`) again this shouldn't work.
controller.maybeShowDisclosure(mActivity, storage, false);
verify(mManager, times(0)).showSnackbar(any(Snackbar.class));
storage.delete();
}
@Test
@Feature({"Webapps"})
public void testUnboundWebApkShowDisclosure() {
String packageName = "unbound";
doReturn(packageName).when(mActivity).getNativeClientPackageName();
doReturn(WebappActivity.ActivityType.WEBAPK).when(mActivity).getActivityType();
verifyShownThenDismissedOnNewCreateStorage(packageName);
}
@Test
@Feature({"Webapps"})
public void testUnboundWebApkNoDisclosureOnExistingStorage() {
verifyNotShownOnExistingStorageWithoutShouldShowDisclosure("unbound");
}
@Test
@Feature({"Webapps"})
public void testTrustedWebActivityShowDisclosure() {
String packageName = "twa";
doReturn(packageName).when(mActivity).getNativeClientPackageName();
doReturn(WebappActivity.ActivityType.TWA).when(mActivity).getActivityType();
verifyShownThenDismissedOnNewCreateStorage(packageName);
}
@Test
@Feature({"Webapps"})
public void testTrustedWebActivityNoDisclosureOnExistingStorage() {
verifyNotShownOnExistingStorageWithoutShouldShowDisclosure("twa");
}
@Test
@Feature({"Webapps"})
public void testBoundWebApkNoDisclosure() {
String packageName = WebApkConstants.WEBAPK_PACKAGE_PREFIX + ".bound";
doReturn(packageName).when(mActivity).getNativeClientPackageName();
doReturn(WebappActivity.ActivityType.WEBAPK).when(mActivity).getActivityType();
verifyNeverShown(packageName);
}
@Test
@Feature({"Webapps"})
public void testWebappNoDisclosure() {
String packageName = "webapp";
doReturn(packageName).when(mActivity).getNativeClientPackageName();
doReturn(WebappActivity.ActivityType.WEBAPP).when(mActivity).getActivityType();
verifyNeverShown(packageName);
}
}
\ No newline at end of file
......@@ -44136,6 +44136,7 @@ Called by update_net_trust_anchors.py.-->
<int value="24" label="MISSING_FILES_NO_SD_CARD"/>
<int value="25" label="OFFLINE_INDICATOR"/>
<int value="26" label="FEED_NTP_STREAM"/>
<int value="27" label="WEBAPK_TWA_PRIVACY_DISCLOSURE"/>
</enum>
<enum name="SnippetOpenMethod">
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