Commit ef89ac8a authored by Peter E Conn's avatar Peter E Conn Committed by Commit Bot

🤝 Ask to clear data when TWA client app is cleared.

Bug: 888447
Change-Id: I3170ed456f8ccc08912e490cdfb1200789533b66
Reviewed-on: https://chromium-review.googlesource.com/c/1283040Reviewed-by: default avatarTed Choc <tedchoc@chromium.org>
Reviewed-by: default avatarPeter Beverloo <peter@chromium.org>
Commit-Queue: Peter Conn <peconn@chromium.org>
Cr-Commit-Position: refs/heads/master@{#601884}
parent 4fb4d4bd
...@@ -795,15 +795,24 @@ by a child template that "extends" this file. ...@@ -795,15 +795,24 @@ by a child template that "extends" this file.
</service> </service>
<!-- Components for Trusted Web Activities --> <!-- Components for Trusted Web Activities -->
<receiver android:name="org.chromium.chrome.browser.browserservices.ClearDataBroadcastReceiver" <receiver android:name="org.chromium.chrome.browser.browserservices.ClientAppBroadcastReceiver"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<data android:scheme="package" /> <data android:scheme="package" />
<action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" /> <action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
<action android:name="android.intent.action.PACKAGE_DATA_CLEARED" /> <action android:name="android.intent.action.PACKAGE_DATA_CLEARED" />
</intent-filter> </intent-filter>
{% if channel in ['default'] %}
<intent-filter>
<action android:name="org.chromium.chrome.browser.browserservices.ClientAppBroadcastReceiver.DEBUG" />
</intent-filter>
{% endif %}
</receiver> </receiver>
<service android:name="org.chromium.chrome.browser.browserservices.ClearDataService"
android:exported="false">
</service>
<!-- Service for decoding images in a sandboxed process. --> <!-- Service for decoding images in a sandboxed process. -->
<service <service
android:description="@string/decoder_description" android:description="@string/decoder_description"
......
// 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.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.content.res.Resources;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.notifications.NotificationBuilderFactory;
import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
/**
* The class responsible for showing the "Would you like to clear your data", the "You are clearing
* your data" and the "Your data has been cleared" notifications that occur when a user uninstalls
* (or clears data for) a Trusted Web Activity client app.
*
* Lifecycle: This class holds no state, create an instance or share as you like.
* Thread safety: Methods on this class can be called from any thread.
*/
public class ClearDataNotificationPublisher {
private static final String NOTIFICATION_TAG_CLEAR_DATA = "ClearDataNotification.ClearData";
/* package */ void showClearDataNotification(Context context, String appName, String url,
boolean uninstall) {
// We base the notification id on the URL so we don't have duplicate Notifications
// offering to clear the same URL.
int notificationId = url.hashCode();
Resources res = context.getResources();
String title = res.getString(uninstall ? R.string.you_have_uninstalled_app
: R.string.you_have_cleared_app, appName);
Notification notification = NotificationBuilderFactory
.createChromeNotificationBuilder(true /* preferCompat */,
ChannelDefinitions.ChannelId.BROWSER)
.setContentTitle(title)
.setContentText(res.getString(R.string.clear_related_data, url))
.addAction(R.drawable.btn_star, res.getString(R.string.clear_data_delete),
ClearDataService.getClearDataIntent(context, url, notificationId))
.addAction(R.drawable.btn_close, res.getString(R.string.close),
ClearDataService.getDismissIntent(context, notificationId))
.setSmallIcon(R.drawable.ic_chrome)
.build();
NotificationManager manager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(NOTIFICATION_TAG_CLEAR_DATA, notificationId, notification);
}
/* package */ void dismissClearDataNotification(Context context, int notificationId) {
NotificationManager manager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
manager.cancel(NOTIFICATION_TAG_CLEAR_DATA, notificationId);
}
}
// 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.app.IntentService;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.Nullable;
import org.chromium.base.Log;
/**
* A background service that clears browsing data.
*
* Lifecycle: This is a Service, so it will be created by the Android Framework.
* Thread safety: As an {@link IntentService}, {@link #onHandleIntent} is called on a background
* thread.
*/
public class ClearDataService extends IntentService {
private static final String TAG = "ClearDataService";
/* package */ static final String ACTION_CLEAR_DATA =
"org.chromium.chrome.browser.browserservices.ClearDataService.CLEAR_DATA";
/* package */ static final String ACTION_DISMISS =
"org.chromium.chrome.browser.browserservices.ClearDataService.DISMISS";
/* package */ static final String EXTRA_NOTIFICATION_ID =
"org.chromium.chrome.browser.browserservices.ClearDataService.NOTIFICATION_ID";
/* package */ static final String EXTRA_URL =
"org.chromium.chrome.browser.browserservices.ClearDataService.URL";
private final ClearDataNotificationPublisher mNotificationManager;
/** Constructor required by Android with default dependencies. */
public ClearDataService() {
this(new ClearDataNotificationPublisher());
}
/** Constructor with dependency injection for testing. */
public ClearDataService(ClearDataNotificationPublisher notificationManager) {
super(TAG);
mNotificationManager = notificationManager;
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
if (intent == null) return;
// ClearDataService is not exported, so as long as we don't let PendingIntents pointing to
// this class leak to other Android applications, we can trust that this code can only be
// called from Chrome (or a Notification that Chrome created).
if (!intent.hasExtra(EXTRA_NOTIFICATION_ID)) {
Log.w(TAG, "Got Intent without Notification Id");
return;
}
int notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, 0);
if (ACTION_CLEAR_DATA.equals(intent.getAction())) {
String url = intent.getStringExtra(EXTRA_URL);
if (url == null) {
Log.w(TAG, "Got Clear Data Intent without URL.");
return;
}
// TODO(peconn): Clear the data!
Log.d(TAG, "Pretending to clear data for %s.", url);
mNotificationManager.dismissClearDataNotification(this, notificationId);
} else if (ACTION_DISMISS.equals(intent.getAction())) {
mNotificationManager.dismissClearDataNotification(this, notificationId);
}
}
/**
* Creates a PendingIntent to clear data for the given |url| and cancel the notification with
* the given |id|.
*/
public static PendingIntent getClearDataIntent(Context context, String url, int id) {
Intent intent = new Intent(context, ClearDataService.class);
intent.setAction(ACTION_CLEAR_DATA);
intent.putExtra(EXTRA_NOTIFICATION_ID, id);
// TODO(peconn): Consider putting this in data instead.
intent.putExtra(EXTRA_URL, url);
// See similar code in {@link getDismissIntent}.
return PendingIntent.getService(context, id, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
/**
* Creates a PendingIntent to dismiss the Notification with the given |id|.
*/
public static PendingIntent getDismissIntent(Context context, int id) {
Intent intent = new Intent(context, ClearDataService.class);
intent.setAction(ACTION_DISMISS);
// Store the notification ID in the intent itself so we can retrieve it later.
intent.putExtra(EXTRA_NOTIFICATION_ID, id);
// Also use the notification ID as the request code so we can have multiple pending intents
// existing at the same time for different applications/urls.
return PendingIntent.getService(context, id, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
}
...@@ -9,6 +9,7 @@ import android.content.Context; ...@@ -9,6 +9,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import org.chromium.base.Log; import org.chromium.base.Log;
import org.chromium.chrome.browser.ChromeVersionInfo;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
...@@ -20,55 +21,89 @@ import java.util.Set; ...@@ -20,55 +21,89 @@ import java.util.Set;
* corresponding to that app. * corresponding to that app.
* *
* Trusted Web Activities are registered to an origin (eg https://www.example.com), however because * Trusted Web Activities are registered to an origin (eg https://www.example.com), however because
* cookies can be scoped more loosely, at 'domain and registry'/TLD+1 level (eg *.example.com) [1], * cookies can be scoped more loosely, at eTLD+1 (or domain) level (eg *.example.com) [1], we need
* we need to clear data at that level. This unfortunately can lead to too much data getting cleared * to clear data at that level. This unfortunately can lead to too much data getting cleared - for
* - for example if the https://maps.google.com TWA is cleared, you'll loose cookies for * example if the https://maps.google.com TWA is cleared, you'll loose cookies for
* https://mail.google.com too. * https://mail.google.com too (since they both share the google.com domain).
* *
* We find this acceptable for two reasons: * We find this acceptable for two reasons:
* - The alternative is *not* clearing some related data - eg a TWA linked to * - The alternative is *not* clearing some related data - eg a TWA linked to
* https://peconn.github.io sets a cookie with Domain=github.io. The TWA is uninstalled and * https://maps.google.com sets a cookie with Domain=google.com. The TWA is uninstalled and
* reinstalled and it can access the cookie it stored before. * reinstalled and it can access the cookie it stored before.
* - We ask the user before clearing the data and while doing so display the scope of data we're * - We ask the user before clearing the data and while doing so display the scope of data we're
* going to wipe. * going to wipe.
* *
* [1] https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Scope_of_cookies * [1] https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Scope_of_cookies
*
* Lifecycle: The lifecycle of this class is managed by Android.
* Thread safety: {@link #onReceive} will be called on the UI thread.
*/ */
public class ClearDataBroadcastReceiver extends BroadcastReceiver { public class ClientAppBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "ClearDataBroadRec"; private static final String TAG = "ClientAppBroadRec";
/**
* An Action that will trigger clearing data on local builds only, for development. The adb
* command to trigger is:
* adb shell am broadcast \
* -n com.google.android.apps.chrome/\
* org.chromium.chrome.browser.browserservices.ClientAppBroadcastReceiver \
* -a org.chromium.chrome.browser.browserservices.ClientAppBroadcastReceiver.DEBUG \
* --ei android.intent.extra.UID 23
*
* But replace 23 with the uid of a Trusted Web Activity Client app.
*/
private static final String ACTION_DEBUG =
"org.chromium.chrome.browser.browserservices.ClientAppBroadcastReceiver.DEBUG";
private static final Set<String> BROADCASTS = new HashSet<>(Arrays.asList( private static final Set<String> BROADCASTS = new HashSet<>(Arrays.asList(
Intent.ACTION_PACKAGE_DATA_CLEARED, Intent.ACTION_PACKAGE_DATA_CLEARED,
Intent.ACTION_PACKAGE_FULLY_REMOVED Intent.ACTION_PACKAGE_FULLY_REMOVED
)); ));
private final ClearDataNotificationPublisher mNotificationPublisher;
private final ClientAppDataRegister mRegister;
/** Constructor with default dependencies for Android. */
public ClientAppBroadcastReceiver() {
this(new ClearDataNotificationPublisher(), new ClientAppDataRegister());
}
/** Constructor to allow dependency injection in tests. */
public ClientAppBroadcastReceiver(ClearDataNotificationPublisher notificationPublisher,
ClientAppDataRegister register) {
mNotificationPublisher = notificationPublisher;
mRegister = register;
}
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
if (intent == null) return; if (intent == null) return;
// Since we only care about ACTION_PACKAGE_DATA_CLEARED and and ACTION_PACKAGE_FULLY_REMOVED // Since we only care about ACTION_PACKAGE_DATA_CLEARED and and ACTION_PACKAGE_FULLY_REMOVED
// which are protected Intents, we can assume that anything that gets past here will be a // which are protected Intents, we can assume that anything that gets past here will be a
// legitimate Intent sent by the system. // legitimate Intent sent by the system.
if (!BROADCASTS.contains(intent.getAction())) return; boolean debug = ChromeVersionInfo.isLocalBuild() && ACTION_DEBUG.equals(intent.getAction());
if (!debug && !BROADCASTS.contains(intent.getAction())) return;
int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
if (uid == -1) return; if (uid == -1) return;
ClientAppDataRegister register;
try (BrowserServicesMetrics.TimingMetric unused = try (BrowserServicesMetrics.TimingMetric unused =
BrowserServicesMetrics.getClientAppDataLoadTimingContext()) { BrowserServicesMetrics.getClientAppDataLoadTimingContext()) {
register = new ClientAppDataRegister();
// The ClientAppDataRegister (because it uses Preferences) is loaded lazily, so to time // The ClientAppDataRegister (because it uses Preferences) is loaded lazily, so to time
// opening the file we must include the first read as well. // opening the file we must include the first read as well.
if (!register.chromeHoldsDataForPackage(uid)) return; if (!mRegister.chromeHoldsDataForPackage(uid)) {
Log.d(TAG, "Chrome holds no data for package.");
return;
}
} }
String appName = register.getAppNameForRegisteredUid(uid); String appName = mRegister.getAppNameForRegisteredUid(uid);
Set<String> domainAndRegistries = register.getDomainAndRegistriesForRegisteredUid(uid); Set<String> origins = mRegister.getDomainsForRegisteredUid(uid);
for (String domainAndRegistry : domainAndRegistries) { for (String origin : origins) {
String action = Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(intent.getAction()) boolean uninstalled = Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(intent.getAction());
? "been uninstalled" : "had its data cleared"; mNotificationPublisher.showClearDataNotification(context, appName, origin, uninstalled);
Log.d(TAG, "%s has just %s, it was linked to %s.", appName, action, domainAndRegistry);
} }
} }
} }
...@@ -19,7 +19,7 @@ import java.util.Set; ...@@ -19,7 +19,7 @@ import java.util.Set;
* {@link ClientAppDataRegister}. It performs three main duties: * {@link ClientAppDataRegister}. It performs three main duties:
* - Holds a cache to deduplicate requests (for performance not correctness). * - Holds a cache to deduplicate requests (for performance not correctness).
* - Transforming the package name into a uid and app label. * - Transforming the package name into a uid and app label.
* - Transforming the origin into a domain and registry (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 TrustedWebActivityUi}. Having more instances won't effect correctness, but will limit the
...@@ -37,14 +37,14 @@ public class ClientAppDataRecorder { ...@@ -37,14 +37,14 @@ public class ClientAppDataRecorder {
/** /**
* Cache so we don't send the same request multiple times. {@link #register} is called on each * Cache so we don't send the same request multiple times. {@link #register} is called on each
* navigation and each call to {@link ClientAppDataRegister#registerPackageForDomainAndRegistry} * navigation and each call to {@link ClientAppDataRegister#registerPackageForDomain}
* modifies SharedPreferences, so we need to cut down on the number of calls. * modifies SharedPreferences, so we need to cut down on the number of calls.
*/ */
private final Set<String> mCache = new HashSet<>(); private final Set<String> mCache = new HashSet<>();
/** Class to allow mocking native calls in unit tests. */ /** Class to allow mocking native calls in unit tests. */
/* package */ static class UrlTransformer { /* package */ static class UrlTransformer {
/* package */ String getDomainAndRegistry(Origin origin) { /* package */ String getDomain(Origin origin) {
return UrlUtilities.getDomainAndRegistry( return UrlUtilities.getDomainAndRegistry(
origin.toString(), true /* includePrivateRegistries */); origin.toString(), true /* includePrivateRegistries */);
} }
...@@ -64,9 +64,9 @@ public class ClientAppDataRecorder { ...@@ -64,9 +64,9 @@ public class ClientAppDataRecorder {
} }
/** /**
* Calls {@link ClientAppDataRegister#registerPackageForDomainAndRegistry}, looking up the uid * Calls {@link ClientAppDataRegister#registerPackageForDomain}, looking up the uid
* and app name for the |packageName|, extracting the domain and registry from the origin and * and app name for the |packageName|, extracting the domain from the origin and deduplicating
* deduplicating multiple requests with the same parameters. * multiple requests with the same parameters.
*/ */
/* 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;
...@@ -82,11 +82,10 @@ public class ClientAppDataRecorder { ...@@ -82,11 +82,10 @@ public class ClientAppDataRecorder {
return; return;
} }
String domainAndRegistry = mUrlTransformer.getDomainAndRegistry(origin); String domain = mUrlTransformer.getDomain(origin);
Log.d(TAG, "Registering %d (%s) for %s", ai.uid, appLabel, domainAndRegistry); Log.d(TAG, "Registering %d (%s) for %s", ai.uid, appLabel, domain);
mClientAppDataRegister.registerPackageForDomainAndRegistry( mClientAppDataRegister.registerPackageForDomain(ai.uid, appLabel, domain);
ai.uid, appLabel, domainAndRegistry);
} catch (PackageManager.NameNotFoundException e) { } catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Couldn't find name for client package %s", packageName); Log.e(TAG, "Couldn't find name for client package %s", packageName);
} }
......
...@@ -40,10 +40,9 @@ public class ClientAppDataRegister { ...@@ -40,10 +40,9 @@ public class ClientAppDataRegister {
/** /**
* Saves to Preferences that the app with |uid| has the application name |appName| and when it * Saves to Preferences that the app with |uid| has the application name |appName| and when it
* 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
* |domainAndRegistry|. * |domain|.
*/ */
/* package */ void registerPackageForDomainAndRegistry( /* package */ void registerPackageForDomain(int uid, String appName, String domain) {
int uid, String appName, String domainAndRegistry) {
// 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));
...@@ -52,12 +51,11 @@ public class ClientAppDataRegister { ...@@ -52,12 +51,11 @@ public class ClientAppDataRegister {
// Store the package name for the UID. // Store the package name for the UID.
mPreferences.edit().putString(createAppNameKey(uid), appName).apply(); mPreferences.edit().putString(createAppNameKey(uid), appName).apply();
// Store the domainAndRegistry for the UID. // Store the domain for the UID.
String key = createDomainAndRegistriesKey(uid); String key = createDomainKey(uid);
Set<String> domainAndRegistries = Set<String> domains = new HashSet<>(mPreferences.getStringSet(key, Collections.emptySet()));
new HashSet<>(mPreferences.getStringSet(key, Collections.emptySet())); domains.add(domain);
domainAndRegistries.add(domainAndRegistry); mPreferences.edit().putStringSet(key, domains).apply();
mPreferences.edit().putStringSet(key, domainAndRegistries).apply();
} }
private void setUids(Set<String> uids) { private void setUids(Set<String> uids) {
...@@ -74,7 +72,7 @@ public class ClientAppDataRegister { ...@@ -74,7 +72,7 @@ public class ClientAppDataRegister {
setUids(uids); setUids(uids);
mPreferences.edit().putString(createAppNameKey(uid), null).apply(); mPreferences.edit().putString(createAppNameKey(uid), null).apply();
mPreferences.edit().putStringSet(createDomainAndRegistriesKey(uid), null).apply(); mPreferences.edit().putStringSet(createDomainKey(uid), null).apply();
} }
/* package */ boolean chromeHoldsDataForPackage(int uid) { /* package */ boolean chromeHoldsDataForPackage(int uid) {
...@@ -89,11 +87,11 @@ public class ClientAppDataRegister { ...@@ -89,11 +87,11 @@ public class ClientAppDataRegister {
} }
/** /**
* Gets all the 'domain and registry's that have been registered for the uid. * Gets all the domains that have been registered for the uid.
* Do not modify the set returned by this method. * Do not modify the set returned by this method.
*/ */
/* package */ Set<String> getDomainAndRegistriesForRegisteredUid(int uid) { /* package */ Set<String> getDomainsForRegisteredUid(int uid) {
return mPreferences.getStringSet(createDomainAndRegistriesKey(uid), Collections.emptySet()); return mPreferences.getStringSet(createDomainKey(uid), Collections.emptySet());
} }
/** /**
...@@ -105,10 +103,10 @@ public class ClientAppDataRegister { ...@@ -105,10 +103,10 @@ public class ClientAppDataRegister {
} }
/** /**
* Creates the Preferences key to access the set of 'domain and registry's for an app. * Creates the Preferences key to access the set of domains for an app.
* If you modify this you'll have to migrate old data. * If you modify this you'll have to migrate old data.
*/ */
private static String createDomainAndRegistriesKey(int uid) { private static String createDomainKey(int uid) {
return uid + ".domainAndRegistry"; return uid + ".domain";
} }
} }
...@@ -3582,6 +3582,15 @@ However, you aren’t invisible. Going private doesn’t hide your browsing from ...@@ -3582,6 +3582,15 @@ However, you aren’t invisible. Going private doesn’t hide your browsing from
<message name="IDS_TWA_RUNNING_IN_CHROME" desc="Message on a snackbar indicating that the current Activity may use Chrome data (the rest of the app may not be)." translateable="false"> <message name="IDS_TWA_RUNNING_IN_CHROME" desc="Message on a snackbar indicating that the current Activity may use Chrome data (the rest of the app may not be)." translateable="false">
Running in Chrome Running in Chrome
</message> </message>
<message name="IDS_YOU_HAVE_UNINSTALLED_APP" desc="Notification title saying the user has uninstalled the given app." translateable="false">
You have uninstalled <ph name="APP_NAME">%1$s<ex>YouTube</ex></ph>.
</message>
<message name="IDS_YOU_HAVE_CLEARED_APP" desc="Notification title saying the user has cleared data for the given app." translateable="false">
You have cleared data for <ph name="APP_NAME">%1$s<ex>YouTube</ex></ph>.
</message>
<message name="IDS_CLEAR_RELATED_DATA" desc="Notification text asking if the user wants to clear data for the given url as they have just uninstalled/cleared an app linked to that URL.">
Would you like to clear data for <ph name="URL">%1$s<ex>youtube.com</ex></ph>?
</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."> <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.">
Tap to copy the URL for this app Tap to copy the URL for this app
......
...@@ -162,7 +162,9 @@ chrome_java_sources = [ ...@@ -162,7 +162,9 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/browserservices/BrowserSessionContentHandler.java", "java/src/org/chromium/chrome/browser/browserservices/BrowserSessionContentHandler.java",
"java/src/org/chromium/chrome/browser/browserservices/BrowserSessionContentUtils.java", "java/src/org/chromium/chrome/browser/browserservices/BrowserSessionContentUtils.java",
"java/src/org/chromium/chrome/browser/browserservices/BrowserSessionDataProvider.java", "java/src/org/chromium/chrome/browser/browserservices/BrowserSessionDataProvider.java",
"java/src/org/chromium/chrome/browser/browserservices/ClearDataBroadcastReceiver.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/ClientAppBroadcastReceiver.java",
"java/src/org/chromium/chrome/browser/browserservices/ClientAppDataRecorder.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/Origin.java", "java/src/org/chromium/chrome/browser/browserservices/Origin.java",
...@@ -2214,6 +2216,9 @@ chrome_junit_test_java_sources = [ ...@@ -2214,6 +2216,9 @@ chrome_junit_test_java_sources = [
"junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetControllerTest.java", "junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetControllerTest.java",
"junit/src/org/chromium/chrome/browser/background_task_scheduler/NativeBackgroundTaskTest.java", "junit/src/org/chromium/chrome/browser/background_task_scheduler/NativeBackgroundTaskTest.java",
"junit/src/org/chromium/chrome/browser/browseractions/BrowserActionsIntentTest.java", "junit/src/org/chromium/chrome/browser/browseractions/BrowserActionsIntentTest.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/ClientAppBroadcastReceiverTest.java",
"junit/src/org/chromium/chrome/browser/browserservices/ClientAppDataRecorderTest.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/OriginTest.java", "junit/src/org/chromium/chrome/browser/browserservices/OriginTest.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 android.app.NotificationManager;
import android.content.Context;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.Shadows;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowNotificationManager;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Feature;
/**
* Tests for {@link ClearDataNotificationPublisher}.
*/
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE,
shadows={ ShadowNotificationManager.class })
public class ClearDataNotificationPublisherTest {
private ClearDataNotificationPublisher mClearDataNotificationPublisher;
private Context mContext;
private ShadowNotificationManager mShadowNotificationManager;
@Before
public void setUp() {
mClearDataNotificationPublisher = new ClearDataNotificationPublisher();
mContext = RuntimeEnvironment.application;
mShadowNotificationManager = Shadows.shadowOf((NotificationManager.from(mContext)));
}
@Test
@Feature("TrustedWebActivities")
public void showNotification() {
mClearDataNotificationPublisher.showClearDataNotification(
mContext, "App", "https://www.example.com/", true);
Assert.assertEquals(1, mShadowNotificationManager.getAllNotifications().size());
}
@Test
@Feature("TrustedWebActivities")
public void singleNotificationPerOrigin() {
mClearDataNotificationPublisher.showClearDataNotification(
mContext, "App", "https://www.website.com/", true);
mClearDataNotificationPublisher.showClearDataNotification(
mContext, "App 2", "https://www.website.com/", true);
Assert.assertEquals(1, mShadowNotificationManager.getAllNotifications().size());
mClearDataNotificationPublisher.showClearDataNotification(
mContext, "App", "https://www.otherwebsite.com/", true);
Assert.assertEquals(2, mShadowNotificationManager.getAllNotifications().size());
}
}
// 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.eq;
import static org.mockito.Mockito.verify;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.Shadows;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowPendingIntent;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Feature;
/**
* Tests for {@link ClearDataService}.
*/
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE,
shadows={ ShadowPendingIntent.class })
public class ClearDataServiceTest {
@Mock public ClearDataNotificationPublisher mNotificationManager;
private ClearDataService mService;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mService = new ClearDataService(mNotificationManager);
}
@Test
@Feature("TrustedWebActivities")
public void dismissNotification() {
int notificationId = 12345;
ShadowPendingIntent intent = Shadows.shadowOf(
ClearDataService.getDismissIntent(RuntimeEnvironment.application, notificationId));
mService.onHandleIntent(intent.getSavedIntent());
verify(mNotificationManager).dismissClearDataNotification(any(), eq(notificationId));
}
}
// 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.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.content.Intent;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Feature;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* Tests for {@link ClientAppBroadcastReceiver}.
*/
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class ClientAppBroadcastReceiverTest {
@Mock public Context mContext;
@Mock public ClientAppDataRegister mDataRegister;
@Mock public ClearDataNotificationPublisher mNotificationManager;
private ClientAppBroadcastReceiver mReceiver;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mReceiver = new ClientAppBroadcastReceiver(mNotificationManager, mDataRegister);
mContext = RuntimeEnvironment.application;
}
private Intent createMockIntent(int id, String action) {
Intent intent = new Intent();
intent.putExtra(Intent.EXTRA_UID, id);
intent.setAction(action);
return intent;
}
private void addToRegister(int id, String appName, Set<String> domainAndRegistries) {
doReturn(true).when(mDataRegister).chromeHoldsDataForPackage(eq(id));
doReturn(appName).when(mDataRegister).getAppNameForRegisteredUid(eq(id));
doReturn(domainAndRegistries)
.when(mDataRegister)
.getDomainsForRegisteredUid(eq(id));
}
/** Makes sure we don't show a notification if we don't have any data for the app. */
@Test
@Feature("TrustedWebActivities")
public void chromeHoldsNoData() {
mReceiver.onReceive(mContext, createMockIntent(12, Intent.ACTION_PACKAGE_FULLY_REMOVED));
verify(mNotificationManager, never())
.showClearDataNotification(any(), anyString(), anyString(), anyBoolean());
}
/** Tests the basic flow. */
@Test
@Feature("TrustedWebActivities")
public void chromeHoldsData() {
int id = 23;
String appName = "App Name";
String domain = "example.com";
Set<String> domains = new HashSet<>(Arrays.asList(domain));
addToRegister(id, appName, domains);
mReceiver.onReceive(mContext, createMockIntent(id, Intent.ACTION_PACKAGE_FULLY_REMOVED));
verify(mNotificationManager)
.showClearDataNotification(any(), eq(appName), eq(domain), eq(true));
}
/** Tests we deal with multiple domains well. */
@Test
@Feature("TrustedWebActivities")
public void multipleDomains() {
int id = 45;
String appName = "App Name 2";
String domain1 = "example.com";
String domain2 = "example2.com";
Set<String> domains = new HashSet<>(Arrays.asList(domain1, domain2));
addToRegister(id, appName, domains);
mReceiver.onReceive(mContext, createMockIntent(id, Intent.ACTION_PACKAGE_FULLY_REMOVED));
verify(mNotificationManager).showClearDataNotification(
any(), eq(appName), eq(domain1), eq(true));
verify(mNotificationManager).showClearDataNotification(
any(), eq(appName), eq(domain2), eq(true));
}
/** Tests we differentiate between app uninstalled and data cleared. */
@Test
@Feature("TrustedWebActivities")
public void onDataClear() {
int id = 23;
String appName = "App Name";
String domain = "example.com";
Set<String> domains = new HashSet<>(Arrays.asList(domain));
addToRegister(id, appName, domains);
mReceiver.onReceive(mContext, createMockIntent(id, Intent.ACTION_PACKAGE_DATA_CLEARED));
verify(mNotificationManager)
.showClearDataNotification(any(), eq(appName), eq(domain), eq(false));
}
}
...@@ -46,7 +46,7 @@ public class ClientAppDataRecorderTest { ...@@ -46,7 +46,7 @@ public class ClientAppDataRecorderTest {
private final ClientAppDataRecorder.UrlTransformer mUrlTransformer = private final ClientAppDataRecorder.UrlTransformer mUrlTransformer =
new ClientAppDataRecorder.UrlTransformer() { new ClientAppDataRecorder.UrlTransformer() {
@Override @Override
String getDomainAndRegistry(Origin origin) { String getDomain(Origin origin) {
return transform(origin); return transform(origin);
} }
}; };
...@@ -80,7 +80,7 @@ public class ClientAppDataRecorderTest { ...@@ -80,7 +80,7 @@ public class ClientAppDataRecorderTest {
@Feature("TrustedWebActivities") @Feature("TrustedWebActivities")
public void testRegister() { public void testRegister() {
mRecorder.register(APP_PACKAGE, ORIGIN); mRecorder.register(APP_PACKAGE, ORIGIN);
verify(mRegister).registerPackageForDomainAndRegistry(APP_UID, APP_NAME, transform(ORIGIN)); verify(mRegister).registerPackageForDomain(APP_UID, APP_NAME, transform(ORIGIN));
} }
@Test @Test
...@@ -88,7 +88,7 @@ public class ClientAppDataRecorderTest { ...@@ -88,7 +88,7 @@ public class ClientAppDataRecorderTest {
public void testDeduplicate() { public void testDeduplicate() {
mRecorder.register(APP_PACKAGE, ORIGIN); mRecorder.register(APP_PACKAGE, ORIGIN);
mRecorder.register(APP_PACKAGE, ORIGIN); mRecorder.register(APP_PACKAGE, ORIGIN);
verify(mRegister).registerPackageForDomainAndRegistry(APP_UID, APP_NAME, transform(ORIGIN)); verify(mRegister).registerPackageForDomain(APP_UID, APP_NAME, transform(ORIGIN));
} }
@Test @Test
...@@ -96,8 +96,8 @@ public class ClientAppDataRecorderTest { ...@@ -96,8 +96,8 @@ public class ClientAppDataRecorderTest {
public void testDifferentOrigins() { public void testDifferentOrigins() {
mRecorder.register(APP_PACKAGE, ORIGIN); mRecorder.register(APP_PACKAGE, ORIGIN);
mRecorder.register(APP_PACKAGE, OTHER_ORIGIN); mRecorder.register(APP_PACKAGE, OTHER_ORIGIN);
verify(mRegister).registerPackageForDomainAndRegistry(APP_UID, APP_NAME, transform(ORIGIN)); verify(mRegister).registerPackageForDomain(APP_UID, APP_NAME, transform(ORIGIN));
verify(mRegister).registerPackageForDomainAndRegistry( verify(mRegister).registerPackageForDomain(
APP_UID, APP_NAME, transform(OTHER_ORIGIN)); APP_UID, APP_NAME, transform(OTHER_ORIGIN));
} }
...@@ -107,6 +107,6 @@ public class ClientAppDataRecorderTest { ...@@ -107,6 +107,6 @@ public class ClientAppDataRecorderTest {
mRecorder.register(MISSING_PACKAGE, ORIGIN); mRecorder.register(MISSING_PACKAGE, ORIGIN);
// Implicitly checking we don't throw. // Implicitly checking we don't throw.
verify(mRegister, times(0)) verify(mRegister, times(0))
.registerPackageForDomainAndRegistry(anyInt(), anyString(), any()); .registerPackageForDomain(anyInt(), anyString(), any());
} }
} }
...@@ -23,8 +23,8 @@ import java.util.Set; ...@@ -23,8 +23,8 @@ import java.util.Set;
public class ClientAppDataRegisterTest { public class ClientAppDataRegisterTest {
private static final int UID = 23; private static final int UID = 23;
private static final String APP_NAME = "Example App"; private static final String APP_NAME = "Example App";
private static final String DOMAIN_AND_REGISTRY = "example.com"; private static final String DOMAIN = "example.com";
private static final String OTHER_DOMAIN_AND_REGISTRY = "otherexample.com"; private static final String OTHER_DOMAIN = "otherexample.com";
private ClientAppDataRegister mRegister; private ClientAppDataRegister mRegister;
...@@ -36,7 +36,7 @@ public class ClientAppDataRegisterTest { ...@@ -36,7 +36,7 @@ public class ClientAppDataRegisterTest {
@Test @Test
@Feature("TrustedWebActivities") @Feature("TrustedWebActivities")
public void registration() { public void registration() {
mRegister.registerPackageForDomainAndRegistry(UID, APP_NAME, DOMAIN_AND_REGISTRY); mRegister.registerPackageForDomain(UID, APP_NAME, DOMAIN);
Assert.assertTrue(mRegister.chromeHoldsDataForPackage(UID)); Assert.assertTrue(mRegister.chromeHoldsDataForPackage(UID));
Assert.assertEquals(APP_NAME, mRegister.getAppNameForRegisteredUid(UID)); Assert.assertEquals(APP_NAME, mRegister.getAppNameForRegisteredUid(UID));
...@@ -45,7 +45,7 @@ public class ClientAppDataRegisterTest { ...@@ -45,7 +45,7 @@ public class ClientAppDataRegisterTest {
@Test @Test
@Feature("TrustedWebActivities") @Feature("TrustedWebActivities")
public void deregistration() { public void deregistration() {
mRegister.registerPackageForDomainAndRegistry(UID, APP_NAME, DOMAIN_AND_REGISTRY); mRegister.registerPackageForDomain(UID, APP_NAME, DOMAIN);
mRegister.removePackage(UID); mRegister.removePackage(UID);
Assert.assertFalse(mRegister.chromeHoldsDataForPackage(UID)); Assert.assertFalse(mRegister.chromeHoldsDataForPackage(UID));
...@@ -55,30 +55,30 @@ public class ClientAppDataRegisterTest { ...@@ -55,30 +55,30 @@ public class ClientAppDataRegisterTest {
@Test @Test
@Feature("TrustedWebActivities") @Feature("TrustedWebActivities")
public void getOrigins() { public void getOrigins() {
mRegister.registerPackageForDomainAndRegistry(UID, APP_NAME, DOMAIN_AND_REGISTRY); mRegister.registerPackageForDomain(UID, APP_NAME, DOMAIN);
mRegister.registerPackageForDomainAndRegistry(UID, APP_NAME, OTHER_DOMAIN_AND_REGISTRY); mRegister.registerPackageForDomain(UID, APP_NAME, OTHER_DOMAIN);
Set<String> origins = mRegister.getDomainAndRegistriesForRegisteredUid(UID); Set<String> origins = mRegister.getDomainsForRegisteredUid(UID);
Assert.assertEquals(2, origins.size()); Assert.assertEquals(2, origins.size());
Assert.assertTrue(origins.contains(DOMAIN_AND_REGISTRY)); Assert.assertTrue(origins.contains(DOMAIN));
Assert.assertTrue(origins.contains(OTHER_DOMAIN_AND_REGISTRY)); Assert.assertTrue(origins.contains(OTHER_DOMAIN));
} }
@Test @Test
@Feature("TrustedWebActivities") @Feature("TrustedWebActivities")
public void clearOrigins() { public void clearOrigins() {
mRegister.registerPackageForDomainAndRegistry(UID, APP_NAME, DOMAIN_AND_REGISTRY); mRegister.registerPackageForDomain(UID, APP_NAME, DOMAIN);
mRegister.registerPackageForDomainAndRegistry(UID, APP_NAME, OTHER_DOMAIN_AND_REGISTRY); mRegister.registerPackageForDomain(UID, APP_NAME, OTHER_DOMAIN);
mRegister.removePackage(UID); mRegister.removePackage(UID);
Set<String> origins = mRegister.getDomainAndRegistriesForRegisteredUid(UID); Set<String> origins = mRegister.getDomainsForRegisteredUid(UID);
Assert.assertTrue(origins.isEmpty()); Assert.assertTrue(origins.isEmpty());
} }
@Test @Test
@Feature("TrustedWebActivities") @Feature("TrustedWebActivities")
public void getAppName() { public void getAppName() {
mRegister.registerPackageForDomainAndRegistry(UID, APP_NAME, DOMAIN_AND_REGISTRY); mRegister.registerPackageForDomain(UID, APP_NAME, DOMAIN);
Assert.assertEquals(APP_NAME, mRegister.getAppNameForRegisteredUid(UID)); Assert.assertEquals(APP_NAME, mRegister.getAppNameForRegisteredUid(UID));
} }
} }
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